# Kế hoạch theo dõi và loại bỏ Activity Completion đã Tracking

## Tổng quan

Kế hoạch này mô tả cách theo dõi các activity completion đã hoàn thành (completion_state = 1) trong bảng `activity_completion_speakup`, sau đó loại bỏ các records trùng lặp sau khi sync data mới từ speakup Moodle.

**Lưu ý**: API `sync-activity-completion-exec` đã chạy và có data trong bảng `activity_completion_speakup` trước đó. Kế hoạch này sẽ tracking data hiện tại, sau đó khi sync data mới, sẽ xóa các records đã tracking để tránh duplicate.

## Mục tiêu

1. **Theo dõi**: Lưu các activity completion đã hoàn thành (completion_state = 1) từ data hiện tại vào tracking table
2. **Lưu trữ**: Lưu danh sách này để tham chiếu sau
3. **Làm sạch**: Sau khi sync data mới, so sánh và xóa các records trùng với tracking (có cùng user_moodle_id và course_module_id)

## Logic xác định Activity Completion cần Tracking

Một activity completion được **tracking** khi:
- Có trong bảng `activity_completion_speakup_{course_id}`
- `completion_state = 1` (đã hoàn thành)
- `user_moodle_id` và `course_module_id` hợp lệ

**Lưu ý**: Chỉ tracking các activity đã hoàn thành (completion_state = 1), không tracking các activity chưa hoàn thành (completion_state = 0, 2, 3).

## Ví dụ minh họa

### Trước khi sync:
- User ID 1 có 25 activity trong `activity_completion_speakup`:
  - 10 activity đã hoàn thành (completion_state = 1)
  - 15 activity chưa hoàn thành (completion_state = 0)
- **Tracking**: Lưu 10 activity đã hoàn thành vào tracking table

### Sau khi sync:
- User ID 1 có 30 activity trong `activity_completion_speakup`:
  - 12 activity đã hoàn thành (10 cũ + 2 mới)
  - 18 activity chưa hoàn thành (15 cũ + 3 mới)
- **So sánh**: Tìm các activity trong tracking table có trong data mới
- **Xóa**: Xóa 10 activity trùng (đã tracking) khỏi `activity_completion_speakup`
- **Kết quả**: Còn lại 2 activity đã hoàn thành (mới) + 18 activity chưa hoàn thành

## Cấu trúc Database

### Bảng mới: `speakup_activity_completion_tracking`

Bảng này sẽ lưu trữ các activity completion đã hoàn thành đã được tracking.

```sql
CREATE TABLE `speakup_activity_completion_tracking` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `course_id` INT NOT NULL COMMENT 'Course ID (38, 39, 45, 46, 47, 68)',
  `user_moodle_id` BIGINT UNSIGNED NOT NULL COMMENT 'User ID từ speakup Moodle',
  `course_module_id` BIGINT UNSIGNED NOT NULL COMMENT 'Course module ID (cm.id)',
  `activity_type` VARCHAR(50) NULL COMMENT 'Loại activity (quiz, url, resource)',
  `activity_name` VARCHAR(255) NULL COMMENT 'Tên activity',
  `completion_state` INT NOT NULL DEFAULT 1 COMMENT 'Trạng thái completion (luôn là 1 - đã hoàn thành)',
  `timemodified` BIGINT UNSIGNED NULL COMMENT 'Thời gian modified từ Moodle',
  `tenant_connection` VARCHAR(255) NULL COMMENT 'Tenant connection name',
  `tracked_at` TIMESTAMP NOT NULL COMMENT 'Thời điểm tracking',
  `created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_course_user_cm` (`course_id`, `user_moodle_id`, `course_module_id`),
  KEY `idx_course_id` (`course_id`),
  KEY `idx_user_moodle_id` (`user_moodle_id`),
  KEY `idx_course_module_id` (`course_module_id`),
  KEY `idx_tenant_connection` (`tenant_connection`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

## Flow Diagram

```
┌─────────────────────────────────────────────────────────────────┐
│ 0. Data hiện tại (đã có)                                         │
│    - activity_completion_speakup_{course_id} có data            │
│    - sync-activity-completion-exec đã chạy                      │
│    - Có các activity completion với completion_state = 1       │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. Tracking activity completion đã hoàn thành                    │
│    API: trackActivityCompletionFromCurrentData                   │
│    POST /api/speakup/track-activity-completion-from-current-data│
│    Input: course_id                                              │
│    Logic:                                                        │
│    - Lấy tất cả records từ activity_completion_speakup          │
│    - Filter completion_state = 1 (đã hoàn thành)                │
│    - Lưu vào tracking table                                     │
│    Output: Số lượng records đã tracking                          │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. Sync data mới từ speakup Moodle                               │
│    API: sync-activity-completion-exec                            │
│    POST /api/speakup/sync-activity-completion-exec               │
│    Input: course_id                                              │
│    Output: Số lượng records đã sync                              │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. So sánh và xóa records trùng với tracking                     │
│    API: removeTrackedActivityCompletionFromSpeakup               │
│    POST /api/speakup/remove-tracked-activity-completion-from-speakup│
│    Input: course_id                                              │
│    Logic:                                                        │
│    - Lấy danh sách từ tracking table                             │
│    - Tìm các records trong activity_completion_speakup có       │
│      (user_moodle_id, course_module_id) trùng với tracking      │
│    - Xóa các records trùng đó                                   │
│    Output: Số lượng records đã xóa                              │
└─────────────────────────────────────────────────────────────────┘
```

## APIs cần tạo

### 1. API tracking activity completion từ data hiện tại

**Endpoint**: `POST /api/speakup/track-activity-completion-from-current-data`

**Mục đích**: Lưu các activity completion đã hoàn thành (completion_state = 1) vào tracking table

**Logic**:
1. Lấy tất cả records từ `activity_completion_speakup_{course_id}` với `completion_state = 1`
2. Lưu vào `speakup_activity_completion_tracking` với các thông tin:
   - `course_id`
   - `user_moodle_id`
   - `course_module_id`
   - `activity_type`
   - `activity_name`
   - `completion_state` (luôn là 1)
   - `timemodified`
   - `tenant_connection`
   - `tracked_at` = now()

**Request**:
```json
{
  "course_id": 45
}
```

**Response**:
```json
{
  "success": true,
  "message": "Tracked 1500 activity completions from current data",
  "course_id": 45,
  "summary": {
    "total_completions_in_speakup": 2000,
    "completed_completions": 1500,
    "tracked_count": 1500,
    "skipped_count": 0
  }
}
```

### 2. API loại bỏ tracked activity completion khỏi speakup table

**Endpoint**: `POST /api/speakup/remove-tracked-activity-completion-from-speakup`

**Mục đích**: Xóa các activity completion đã tracking khỏi bảng `activity_completion_speakup_{course_id}`

**Logic**:
1. Lấy danh sách `(user_moodle_id, course_module_id)` từ `speakup_activity_completion_tracking` theo `course_id`
2. Xóa các records trong `activity_completion_speakup_{course_id}` có `(user_moodle_id, course_module_id)` trùng với tracking

**Request**:
```json
{
  "course_id": 45
}
```

**Response**:
```json
{
  "success": true,
  "message": "Removed 1500 tracked activity completions from activity_completion_speakup table",
  "course_id": 45,
  "removed_count": 1500,
  "remaining_count": 500
}
```

### 3. API lấy danh sách activity completion đã tracking

**Endpoint**: `POST /api/speakup/get-activity-completion-tracking`

**Mục đích**: Xem danh sách các activity completion đã được tracking

**Request**:
```json
{
  "course_id": 45,
  "user_moodle_id": 123  // optional
}
```

**Response**:
```json
{
  "success": true,
  "course_id": 45,
  "total_count": 1500,
  "activity_completions": [
    {
      "id": 1,
      "course_id": 45,
      "user_moodle_id": 123,
      "course_module_id": 16004,
      "activity_type": "quiz",
      "activity_name": "Quiz 1",
      "completion_state": 1,
      "timemodified": 1640995200,
      "tenant_connection": "tenant1",
      "tracked_at": "2025-01-15 10:30:00",
      "created_at": "2025-01-15 10:30:00"
    }
  ]
}
```

### 4. API xóa tracking data (optional)

**Endpoint**: `POST /api/speakup/clear-activity-completion-tracking`

**Mục đích**: Xóa dữ liệu tracking cho một course (khi cần reset)

**Request**:
```json
{
  "course_id": 45
}
```

**Response**:
```json
{
  "success": true,
  "message": "Cleared tracking data for course 45",
  "course_id": 45,
  "deleted_count": 1500
}
```

## Các bước Implementation

### Bước 1: Tạo Migration

Tạo file migration để tạo bảng `speakup_activity_completion_tracking`:

```bash
php artisan make:migration create_speakup_activity_completion_tracking_table
```

### Bước 2: Tạo Model

Tạo model `SpeakupActivityCompletionTracking`:

```bash
php artisan make:model SpeakupActivityCompletionTracking
```

### Bước 3: Tạo Controller Methods

Thêm các methods vào `SpeakupActivitySyncController`:
1. `trackActivityCompletionFromCurrentData()` - Lưu activity completion đã hoàn thành vào tracking
2. `removeTrackedActivityCompletionFromSpeakup()` - Xóa tracked activity completion khỏi speakup table
3. `getActivityCompletionTracking()` - Xem danh sách tracking
4. `clearActivityCompletionTracking()` - Xóa tracking data (optional)

### Bước 4: Thêm Routes

Thêm các routes vào `routes/api.php` trong prefix `speakup`:

```php
Route::prefix('speakup')->controller(SpeakupActivitySyncController::class)->group(function () {
    // ... existing routes ...
    
    // API mới để tracking activity completion
    Route::post('/track-activity-completion-from-current-data', 'trackActivityCompletionFromCurrentData')
        ->name('speakup.trackActivityCompletionFromCurrentData');
    
    Route::post('/remove-tracked-activity-completion-from-speakup', 'removeTrackedActivityCompletionFromSpeakup')
        ->name('speakup.removeTrackedActivityCompletionFromSpeakup');
    
    Route::post('/get-activity-completion-tracking', 'getActivityCompletionTracking')
        ->name('speakup.getActivityCompletionTracking');
    
    Route::post('/clear-activity-completion-tracking', 'clearActivityCompletionTracking')
        ->name('speakup.clearActivityCompletionTracking');
});
```

### Bước 5: Implement logic trong Controller

**Method `trackActivityCompletionFromCurrentData()`**:

1. Lấy tất cả records từ `activity_completion_speakup_{course_id}` với `completion_state = 1`
2. Lưu vào `speakup_activity_completion_tracking` với đầy đủ thông tin
3. Sử dụng batch insert để tối ưu performance

**Method `removeTrackedActivityCompletionFromSpeakup()`**:

1. Sử dụng EXISTS subquery để tìm các records trong `activity_completion_speakup_{course_id}` có `(user_moodle_id, course_module_id)` trùng với tracking
2. Với số lượng lớn, có thể cần chunk delete để tránh lock table quá lâu
3. Sử dụng transaction để đảm bảo data consistency
4. Log số lượng records đã xóa

## Workflow sử dụng

### Quy trình chuẩn:

1. **Bước 1**: Tracking activity completion đã hoàn thành từ data hiện tại
   ```bash
   POST /api/speakup/track-activity-completion-from-current-data
   {
     "course_id": 45
   }
   ```
   → Lưu các activity completion có `completion_state = 1` vào tracking table

2. **Bước 2**: Sync data mới từ speakup Moodle
   ```bash
   POST /api/speakup/sync-activity-completion-exec
   {
     "course_id": 45
   }
   ```
   → Sync data mới vào `activity_completion_speakup_{course_id}`

3. **Bước 3**: Xóa tracked activity completion khỏi speakup table
   ```bash
   POST /api/speakup/remove-tracked-activity-completion-from-speakup
   {
     "course_id": 45
   }
   ```
   → Xóa các records trùng với tracking (có cùng user_moodle_id và course_module_id)
   → Chỉ giữ lại các activity completion mới hoặc khác với tracking

### Quy trình lặp lại:

Sau khi xóa, có thể:
- Tracking lại sau khi có data mới
- Sync lại và lặp lại quy trình

## Lưu ý quan trọng

1. **Unique constraint**: Bảng tracking có unique constraint trên `(course_id, user_moodle_id, course_module_id)` để tránh duplicate
2. **Chỉ tracking completion_state = 1**: Chỉ lưu các activity đã hoàn thành, không lưu các trạng thái khác (0, 2, 3)
3. **Performance**: 
   - Khi xóa nhiều records, sử dụng batch delete hoặc chunk để tránh timeout
   - Có thể sử dụng raw query với `IN` clause để xóa hiệu quả hơn
4. **Transaction**: Sử dụng database transaction cho các operations quan trọng
5. **Logging**: Log đầy đủ các thao tác để debug
6. **Tenant connection**: Bảng tracking có thể lưu trong master DB (default connection) để dễ quản lý
7. **Data consistency**: Đảm bảo `(user_moodle_id, course_module_id)` trong tracking table match với speakup table

## Migration File Template

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('speakup_activity_completion_tracking', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->integer('course_id')->comment('Course ID (38, 39, 45, 46, 47, 68)');
            $table->unsignedBigInteger('user_moodle_id')->comment('User ID từ speakup Moodle');
            $table->unsignedBigInteger('course_module_id')->comment('Course module ID (cm.id)');
            $table->string('activity_type', 50)->nullable();
            $table->string('activity_name')->nullable();
            $table->integer('completion_state')->default(1)->comment('Luôn là 1 - đã hoàn thành');
            $table->unsignedBigInteger('timemodified')->nullable();
            $table->string('tenant_connection')->nullable();
            $table->timestamp('tracked_at');
            $table->timestamps();
            
            $table->unique(['course_id', 'user_moodle_id', 'course_module_id'], 'unique_course_user_cm');
            $table->index('course_id');
            $table->index('user_moodle_id');
            $table->index('course_module_id');
            $table->index('tenant_connection');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('speakup_activity_completion_tracking');
    }
};
```

## Model Template

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class SpeakupActivityCompletionTracking extends Model
{
    use HasFactory;

    protected $table = 'speakup_activity_completion_tracking';

    protected $fillable = [
        'course_id',
        'user_moodle_id',
        'course_module_id',
        'activity_type',
        'activity_name',
        'completion_state',
        'timemodified',
        'tenant_connection',
        'tracked_at',
    ];

    protected $casts = [
        'course_id' => 'integer',
        'user_moodle_id' => 'integer',
        'course_module_id' => 'integer',
        'completion_state' => 'integer',
        'timemodified' => 'integer',
        'tracked_at' => 'datetime',
    ];
}
```

## Logic xóa records trùng (Chi tiết)

Để xóa các records trùng, cần so sánh trên 2 fields: `user_moodle_id` và `course_module_id`.

### Cách 1: Sử dụng whereIn với chunk

```php
// Lấy danh sách (user_moodle_id, course_module_id) từ tracking
$trackedPairs = SpeakupActivityCompletionTracking::where('course_id', $courseId)
    ->select('user_moodle_id', 'course_module_id')
    ->get();

// Chunk để xử lý từng batch
$trackedPairs->chunk(1000)->each(function ($chunk) use ($completionTable) {
    $userIds = $chunk->pluck('user_moodle_id')->unique()->toArray();
    $cmIds = $chunk->pluck('course_module_id')->unique()->toArray();
    
    // Xóa records có (user_moodle_id, course_module_id) trong chunk
    DB::table($completionTable)
        ->whereIn('user_moodle_id', $userIds)
        ->whereIn('course_module_id', $cmIds)
        ->where(function ($query) use ($chunk) {
            foreach ($chunk as $pair) {
                $query->orWhere(function ($q) use ($pair) {
                    $q->where('user_moodle_id', $pair->user_moodle_id)
                      ->where('course_module_id', $pair->course_module_id);
                });
            }
        })
        ->delete();
});
```

### Cách 2: Sử dụng raw query với IN clause (Hiệu quả hơn)

```php
// Tạo array of pairs
$trackedPairs = SpeakupActivityCompletionTracking::where('course_id', $courseId)
    ->get(['user_moodle_id', 'course_module_id']);

// Chunk để xử lý từng batch
$trackedPairs->chunk(1000)->each(function ($chunk) use ($completionTable) {
    $conditions = [];
    $bindings = [];
    
    foreach ($chunk as $pair) {
        $conditions[] = '(user_moodle_id = ? AND course_module_id = ?)';
        $bindings[] = $pair->user_moodle_id;
        $bindings[] = $pair->course_module_id;
    }
    
    $whereClause = implode(' OR ', $conditions);
    
    DB::table($completionTable)
        ->whereRaw($whereClause, $bindings)
        ->delete();
});
```

### Cách 3: Sử dụng EXISTS với Subquery (Hiệu quả và chính xác - KHUYẾN NGHỊ)

**Phiên bản đơn giản** (phù hợp với số lượng vừa phải):
```php
DB::table($completionTable)
    ->whereExists(function ($query) use ($courseId, $completionTable) {
        $query->select(DB::raw(1))
              ->from('speakup_activity_completion_tracking')
              ->whereColumn('speakup_activity_completion_tracking.user_moodle_id', $completionTable . '.user_moodle_id')
              ->whereColumn('speakup_activity_completion_tracking.course_module_id', $completionTable . '.course_module_id')
              ->where('speakup_activity_completion_tracking.course_id', $courseId);
    })
    ->delete();
```

**Phiên bản chunk delete** (phù hợp với số lượng lớn, tránh lock table):
```php
// Lấy danh sách user_moodle_id từ tracking để chunk delete
$userIds = SpeakupActivityCompletionTracking::where('course_id', $courseId)
    ->distinct()
    ->pluck('user_moodle_id')
    ->toArray();

// Chunk theo user_moodle_id
$chunks = array_chunk($userIds, 100);

foreach ($chunks as $chunk) {
    DB::table($completionTable)
        ->whereIn('user_moodle_id', $chunk)
        ->whereExists(function ($query) use ($courseId, $completionTable) {
            $query->select(DB::raw(1))
                  ->from('speakup_activity_completion_tracking')
                  ->whereColumn('speakup_activity_completion_tracking.user_moodle_id', $completionTable . '.user_moodle_id')
                  ->whereColumn('speakup_activity_completion_tracking.course_module_id', $completionTable . '.course_module_id')
                  ->where('speakup_activity_completion_tracking.course_id', $courseId);
        })
        ->delete();
}
```

**Lưu ý**: 
- Cách 3 sử dụng EXISTS là an toàn và chính xác nhất với MySQL
- Phiên bản chunk delete phù hợp với số lượng records lớn (10000+) để tránh lock table quá lâu
- Cần đảm bảo có indexes trên `(user_moodle_id, course_module_id)` trong cả 2 bảng để tối ưu performance
- Có thể điều chỉnh kích thước chunk (100, 500, 1000) tùy theo lượng dữ liệu và performance của database

## Testing Checklist

- [ ] Test tracking activity completion từ data hiện tại
- [ ] Test chỉ tracking completion_state = 1
- [ ] Test unique constraint (không lưu duplicate khi tracking lại)
- [ ] Test xóa tracked activity completion khỏi speakup table
- [ ] Test xóa với số lượng lớn records (10000+)
- [ ] Test với nhiều course_id khác nhau
- [ ] Test với tenant connection
- [ ] Test rollback khi có lỗi (transaction)
- [ ] Test xem danh sách tracking
- [ ] Test xóa tracking data (clear)
- [ ] Test với data empty (không có completion_state = 1)
- [ ] Test performance với số lượng lớn (100000+ records)

## Future Enhancements

1. **Auto-tracking**: Tự động tracking sau khi sync thành công
2. **Batch operations**: Hỗ trợ batch tracking/remove cho nhiều courses cùng lúc
3. **Statistics**: Thêm API để xem thống kê activity completion theo course, user, activity type
4. **Retention policy**: Tự động xóa tracking data cũ sau một thời gian nhất định
5. **Export/Import**: Hỗ trợ export/import tracking data
6. **Dry-run mode**: Thêm mode để preview các records sẽ bị xóa trước khi thực sự xóa
7. **Comparison report**: API để so sánh data tracking với data hiện tại trong speakup table

