# Triển khai tính năng Upload CV trong form ứng tuyển

## Tổng quan
Đã thêm tính năng upload file CV vào form ứng tuyển và thay đổi cách xử lý từ JSON sang FormData để hỗ trợ upload file.

## Các thay đổi đã thực hiện

### 1. Cập nhật View (_Apply.cshtml)
**File:** `ladaexpress.vn/Views/JobPost/_Apply.cshtml`

**Thay đổi:**
- Thêm trường input file upload CV
- Hỗ trợ các định dạng: PDF, DOC, DOCX, JPG, PNG
- Giới hạn kích thước file: 5MB
- Thêm validation message và hướng dẫn cho người dùng

```html
<div class="col-lg-12">
    <div class="mb-3">
        <label for="ctrlCvFile" class="form-label">Upload CV <span class="text-muted">(PDF, DOC, DOCX, JPG, PNG - Tối đa 5MB)</span></label>
        <input id="ctrlCvFile" name="cvFile" class="form-control" type="file" 
            accept=".pdf,.doc,.docx,.jpg,.jpeg,.png" />
        <div class="invalid-feedback">
            Vui lòng chọn file CV
        </div>
        <div class="form-text">
            Hỗ trợ các định dạng: PDF, DOC, DOCX, JPG, PNG. Kích thước tối đa: 5MB
        </div>
    </div>
</div>
```

### 2. Cập nhật JavaScript (apply.js)
**File:** `ladaexpress.vn/wwwroot/js/apply.js`

**Thay đổi chính:**
- Thay đổi từ JSON sang FormData
- Thêm validation cho file upload
- Cải thiện error handling

**Trước:**
```javascript
var model = {
    JobPostId: $('#ctrlJobpostId').val(),
    FullName: $('#ctrlFullName').val(),
    // ...
};

$.ajax({
    url: '/api/Candidate/Apply',
    method: 'POST',
    contentType: 'application/json',
    data: JSON.stringify(model),
    // ...
});
```

**Sau:**
```javascript
// Tạo FormData thay vì JSON object
var formData = new FormData();
formData.append('JobPostId', $('#ctrlJobpostId').val());
formData.append('FullName', $('#ctrlFullName').val());
// ...

// Thêm file CV nếu có
if (cvFile) {
    formData.append('CvFile', cvFile);
}

$.ajax({
    url: '/api/Candidate/Apply',
    method: 'POST',
    data: formData,
    processData: false,  // Không xử lý data
    contentType: false,  // Không set content-type
    // ...
});
```

**Validation file upload:**
```javascript
$('#ctrlCvFile').on('change', function() {
    var file = this.files[0];
    var maxSize = 5 * 1024 * 1024; // 5MB
    var allowedTypes = ['application/pdf', 'application/msword', ...];
    
    if (file) {
        // Kiểm tra kích thước file
        if (file.size > maxSize) {
            alert('File CV không được vượt quá 5MB. Vui lòng chọn file khác.');
            fileInput.val('');
            return;
        }
        
        // Kiểm tra định dạng file
        if (!allowedTypes.includes(file.type)) {
            alert('Chỉ hỗ trợ các định dạng: PDF, DOC, DOCX, JPG, PNG.');
            fileInput.val('');
            return;
        }
    }
});
```

### 3. Cập nhật Controller
**File:** `ladaexpress.vn/Controllers/CandidateController.cs`

**Thay đổi:**
```csharp
// Trước
[HttpPost("Apply")]
public async Task<IActionResult> Apply([FromBody] ApplyModel applyModel)

// Sau  
[HttpPost("Apply")]
public async Task<IActionResult> Apply([FromForm] ApplyModel applyModel)
```

### 4. Cập nhật Model với Validation
**File:** `ladaexpress.vn/Infrastructures/DTO/Models/Hr/ApplyModel.cs`

**Thêm validation attributes:**
```csharp
public class ApplyModel
{
    [Required(ErrorMessage = "Vui lòng chọn vị trí ứng tuyển")]
    public int JobPostId { get; set; }
    
    [Required(ErrorMessage = "Vui lòng nhập họ và tên")]
    [StringLength(100, ErrorMessage = "Họ và tên không được vượt quá 100 ký tự")]
    public string FullName { get; set; }
    
    [Required(ErrorMessage = "Vui lòng nhập số điện thoại")]
    [Phone(ErrorMessage = "Số điện thoại không hợp lệ")]
    public string PhoneNumber { get; set; }
    
    // ... các trường khác
    
    /// <summary>
    /// File CV để upload (chỉ sử dụng khi tạo/cập nhật)
    /// </summary>
    public IFormFile? CvFile { get; set; }
}
```

## Cấu hình đã có sẵn

### 1. AutoMapper Configuration
Đã có mapping từ ApplyModel sang Candidate:
```csharp
cfg.CreateMap<ApplyModel, Candidate>()
.ForMember(dest => dest.JobId, opt => opt.MapFrom(src => src.JobPostId));
```

### 2. CandidateService
Đã có logic xử lý upload CV:
```csharp
// Xử lý upload CV nếu có
if (model.CvFile != null)
{
    var cvUploadResult = await _cvUploadService.UploadCvAsync(model.CvFile);
    if (cvUploadResult.Result == Result.Success)
    {
        candidate.CvFileName = cvUploadResult.FileName;
        candidate.CvFilePath = cvUploadResult.FilePath;
        candidate.CvFileSize = cvUploadResult.FileSize;
        candidate.CvFileType = cvUploadResult.FileType;
        candidate.CvFileUrl = cvUploadResult.FileUrl;
        candidate.CvUploadedTime = cvUploadResult.UploadedTime;
    }
}
```

### 3. CvUploadService
Đã có service xử lý upload và convert CV:
- Hỗ trợ convert DOC/DOCX sang PDF
- Tối ưu hóa storage
- Validation file type và size

## File Test
**File:** `ladaexpress.vn/wwwroot/test-apply-cv.html`

Tạo file test để kiểm tra tính năng upload CV:
- Form đầy đủ các trường
- Validation client-side
- Test upload file
- Hiển thị kết quả

## Cách sử dụng

### 1. Truy cập form ứng tuyển
- Người dùng click vào nút "Ứng tuyển" trên trang job detail
- Form modal hiển thị với trường upload CV mới

### 2. Upload CV
- Chọn file CV (PDF, DOC, DOCX, JPG, PNG)
- Hệ thống validate kích thước (max 5MB) và định dạng
- File được upload và xử lý tự động

### 3. Xử lý server-side
- File được upload qua CvUploadService
- DOC/DOCX tự động convert sang PDF
- Thông tin file được lưu vào database
- Candidate record được tạo với thông tin CV

## Lưu ý kỹ thuật

### 1. FormData vs JSON
- **FormData:** Hỗ trợ upload file, không cần set contentType
- **JSON:** Chỉ hỗ trợ text data, không upload được file

### 2. File Validation
- **Client-side:** Kiểm tra ngay khi chọn file (UX tốt hơn)
- **Server-side:** Validation cuối cùng trong CvUploadService

### 3. Error Handling
- Hiển thị lỗi chi tiết cho người dùng
- Log lỗi server-side để debug

## Test Cases

### 1. Test thành công
- Upload file PDF hợp lệ < 5MB
- Điền đầy đủ thông tin
- Kết quả: Tạo candidate thành công với CV

### 2. Test validation
- File > 5MB → Hiển thị lỗi
- File không đúng định dạng → Hiển thị lỗi
- Thiếu thông tin bắt buộc → Hiển thị validation

### 3. Test không có CV
- Không chọn file CV
- Kết quả: Vẫn tạo candidate thành công, không có thông tin CV

## Kết luận
Tính năng upload CV đã được triển khai thành công với:
- ✅ Form UI với trường upload file
- ✅ Client-side validation
- ✅ FormData handling
- ✅ Server-side processing
- ✅ File conversion (DOC/DOCX → PDF)
- ✅ Database integration
- ✅ Error handling
- ✅ Test file để kiểm tra
