# API Quản Lý Xe Tải - Tạo và Cập Nhật

## Thông tin Endpoint

**URL:** `POST /api/Truck/CreateOrUpdate`

**Authorization:** Bearer Token - Role: `Truck.CreateOrUpdate`

**Content-Type:** `application/json`

## Mô tả

API này dùng để **tạo mới** hoặc **cập nhật** thông tin xe tải trong hệ thống. Endpoint này xử lý cả hai chức năng dựa trên giá trị của trường `Id`:
- **Id = 0**: Tạo xe tải mới
- **Id > 0**: Cập nhật xe tải đã tồn tại

---

## Request Body

### Schema

```json
{
  "Id": 0,
  "LicensePlate": "string",
  "Brand": "string",
  "Model": "string",
  "AgencyId": 0,
  "MaxPayload": 0.0,
  "CargoLength": 0,
  "CargoWidth": 0,
  "CargoHeight": 0,
  "OperatingStatus": 0,
  "Status": 0,
  "CurrentDriverId": 0,
  "ManufacturingYear": 0,
  "RegistrationInfo": "string",
  "FuelQuotaPer100Km": 0.0,
  "UpdatedBy": 0,
  "UpdatedByUserName": "string"
}
```

### Các trường dữ liệu

| Tên trường | Kiểu dữ liệu | Bắt buộc | Có thể cập nhật | Mô tả |
|------------|--------------|----------|-----------------|-------|
| `Id` | `int` | Có | Không | **0** = Tạo mới, **> 0** = Cập nhật (ID xe tải) |
| `LicensePlate` | `string` | Có | Có | Biển số xe (tối đa 20 ký tự, phải unique) |
| `Brand` | `string?` | Không | Có | Thương hiệu xe tải (ví dụ: "Kia", "Hyundai", "Isuzu"), tối đa 50 ký tự |
| `Model` | `string?` | Không | Có | Model xe tải (ví dụ: "K200", "Porter", "D-Max"), tối đa 50 ký tự |
| `AgencyId` | `int` | Có | Có | ID chi nhánh mà xe tải thuộc về (phải > 0) |
| `MaxPayload` | `decimal?` | Không | Có | Tải trọng tối đa cho phép (tấn), nullable |
| `CargoLength` | `int?` | Không | Có | Chiều dài khu vực chứa hàng (cm), nullable |
| `CargoWidth` | `int?` | Không | Có | Chiều rộng khu vực chứa hàng (cm), nullable |
| `CargoHeight` | `int?` | Không | Có | Chiều cao khu vực chứa hàng (cm), nullable |
| `OperatingStatus` | `int` (enum) | Không | **CHỈ KHI TẠO** | Trạng thái vận hành - Quản lý bởi API check-in/check-out |
| `Status` | `int` (enum) | Không | **CHỈ KHI TẠO** | Trạng thái xe - Quản lý bởi API check-in/check-out |
| `CurrentDriverId` | `int` | Không | **CHỈ KHI TẠO** | ID tài xế - Quản lý bởi API check-in/check-out |
| `ManufacturingYear` | `int?` | Không | Có | Năm sản xuất (>= 1900), nullable |
| `RegistrationInfo` | `string?` | Không | Có | Thông tin đăng kiểm (tối đa 200 ký tự), nullable |
| `FuelQuotaPer100Km` | `decimal?` | Không | Có | Định mức khoán xăng dầu (lít/100km), nullable |
| `UpdatedBy` | `int` | Có | - | ID người tạo/cập nhật |
| `UpdatedByUserName` | `string` | Có | - | Tên đăng nhập của người tạo/cập nhật |

### Enums

#### OperatingStatus
- `0` - **Stop**: Xe đang dừng đỗ
- `1` - **Running**: Xe đang hoạt động

#### TruckStatus
- `0` - **Normal**: Xe hoạt động bình thường
- `1` - **Broken**: Xe hỏng
- `2` - **Maintenance**: Xe đang bảo trì
- `3` - **Retired**: Xe đã ngưng hoạt động

---

## Response

### Success Response

**Status Code:** `200 OK`

```json
{
  "Result": 0,
  "Message": null
}
```

### Response Schema

| Tên trường | Kiểu dữ liệu | Mô tả |
|------------|--------------|-------|
| `Result` | `int` (enum) | `0` = Success, `2` = Failed, `3` = SystemError |
| `Message` | `string` | Thông báo lỗi (nếu có) |

---

## Ví dụ

### 1. Tạo xe tải mới (Id = 0)

**Request:**
```json
{
  "Id": 0,
  "LicensePlate": "29C-12345",
  "Brand": "Kia",
  "Model": "K200",
  "AgencyId": 1,
  "MaxPayload": 5.5,
  "CargoLength": 420,
  "CargoWidth": 180,
  "CargoHeight": 200,
  "OperatingStatus": 0,
  "Status": 0,
  "CurrentDriverId": 0,
  "ManufacturingYear": 2020,
  "RegistrationInfo": "Đăng kiểm đến 31/12/2026",
  "FuelQuotaPer100Km": 11.5,
  "UpdatedBy": 123,
  "UpdatedByUserName": "admin"
}
```

**Response:**
```json
{
  "Result": 0,
  "Message": null
}
```

---

### 2. Cập nhật thông tin xe tải (Id > 0)

**Request Example:**
```json
{
  "Id": 15,
  "LicensePlate": "29C-54321",
  "Brand": "Hyundai",
  "Model": "Porter",
  "AgencyId": 2,
  "MaxPayload": 6.0,
  "CargoLength": null,
  "CargoWidth": null,
  "CargoHeight": null,
  "ManufacturingYear": 2021,
  "RegistrationInfo": "Đăng kiểm đến 30/06/2027",
  "FuelQuotaPer100Km": null,
  "UpdatedBy": 123,
  "UpdatedByUserName": "admin"
}
```

**Lưu ý:** Các trường `OperatingStatus`, `Status`, `CurrentDriverId` không cần gửi khi update vì sẽ bị bỏ qua.

**Response:**
```json
{
  "Result": 0,
  "Message": null
}
```

---

## Error Response Examples

**Xe tải không tồn tại (khi update):**
```json
{
  "Result": 2,
  "Message": "Không tìm thấy xe tải nào"
}
```

**Duplicate License Plate:**
```json
{
  "Result": 3,
  "Message": "Violation of UNIQUE KEY constraint 'UK_Trucks_LicensePlate'..."
}
```

**Missing Required Fields:**
```json
{
  "Result": 2,
  "Message": "Validation failed"
}
```

---

## So sánh Create vs Update

| Khía cạnh | Create (Id = 0) | Update (Id > 0) |
|-----------|-----------------|-----------------|
| **Id** | Phải = 0 | Phải > 0 và xe phải tồn tại |
| **LicensePlate** | Phải unique | Phải unique (trừ chính xe đang update) |
| **Mapping** | Tự động qua `ToEntity()` | Gán từng trường cụ thể |
| **Trường thiếu** | Dùng giá trị mặc định | Giữ nguyên giá trị cũ trong DB |
| **Validation** | Kiểm tra unique constraints | Kiểm tra xe tồn tại + unique constraints |

---

## Lưu ý quan trọng

> [!IMPORTANT]
> **Khi tạo mới (Id = 0):**
> - Biển số xe (`LicensePlate`) phải là duy nhất trong hệ thống
> - Chi nhánh (`AgencyId`) phải tồn tại trong bảng Agency
> - Bắt buộc `Id = 0`
>
> **Khi cập nhật (Id > 0):**
> - Xe tải phải tồn tại trong hệ thống
> - Biển số xe mới phải là duy nhất nếu thay đổi
> - Chi nhánh mới phải tồn tại trong bảng Agency
> - Bắt buộc `Id > 0`

> [!TIP]
> **Giá trị mặc định:**
> - `OperatingStatus = Stop` (0)
> - `Status = Normal` (0)
> - `FuelQuotaPer100Km = 0`
> - `CurrentDriverId = 0` (không có tài xế phụ trách)
>
> **Đơn vị đo:**
> - Kích thước thùng hàng (`CargoLength`, `CargoWidth`, `CargoHeight`): **cm**
> - Tải trọng (`MaxPayload`): **tấn**
> - Định mức xăng dầu (`FuelQuotaPer100Km`): **lít/100km**
>
> **Các trường có thể cập nhật:**
> - `LicensePlate`: Biển số xe
> - `Brand`: Thương hiệu xe tải
> - `Model`: Model xe tải
> - `AgencyId`: Khi điều chuyển xe sang chi nhánh khác
> - `MaxPayload`, `CargoLength`, `CargoWidth`, `CargoHeight`: Thông số kỹ thuật
> - `ManufacturingYear`: Năm sản xuất
> - `RegistrationInfo`: Khi gia hạn đăng kiểm
> - `FuelQuotaPer100Km`: Định mức xăng dầu
>
> **Các trường KHÔNG thể cập nhật qua API này:**
> - `OperatingStatus`: Dùng API check-in/check-out
> - `Status`: Dùng API check-in/check-out
> - `CurrentDriverId`: Dùng API check-in/check-out

> [!WARNING]
> - API này yêu cầu role `Truck.CreateOrUpdate`
> - Khi cập nhật, các trường nullable (`MaxPayload`, `CargoLength`, etc.) nếu gửi `null` sẽ giữ nguyên giá trị cũ
> - **KHÔNG thể cập nhật** `OperatingStatus`, `Status`, `CurrentDriverId` qua API này - dùng API check-in/check-out riêng
> - Người dùng cần có quyền này trong Access Token

---

## Frontend Integration Guide

### TypeScript Interfaces

```typescript
// Enums
export enum OperatingStatus {
  Stop = 0,
  Running = 1
}

export enum TruckStatus {
  Normal = 0,
  Broken = 1,
  Maintenance = 2,
  Retired = 3
}

export enum ApiResult {
  Success = 0,
  Failed = 2,
  SystemError = 3
}

// Request Model
export interface TruckModel {
  Id: number;                        // 0 = Create, >0 = Update
  LicensePlate: string;              // Required, max 20 chars
  Brand?: string | null;             // Optional, max 50 chars (e.g., "Kia", "Hyundai")
  Model?: string | null;             // Optional, max 50 chars (e.g., "K200", "Porter")
  AgencyId: number;                  // Required, must be > 0
  MaxPayload?: number | null;        // Optional, tấn
  CargoLength?: number | null;       // Optional, cm
  CargoWidth?: number | null;        // Optional, cm
  CargoHeight?: number | null;       // Optional, cm
  OperatingStatus?: OperatingStatus; // Only for Create
  Status?: TruckStatus;              // Only for Create
  CurrentDriverId?: number;          // Only for Create
  ManufacturingYear?: number | null; // Optional, >= 1900
  RegistrationInfo?: string | null;  // Optional, max 200 chars
  FuelQuotaPer100Km?: number | null; // Optional, lít/100km
  UpdatedBy: number;                 // Required
  UpdatedByUserName: string;         // Required
}

// Response Model
export interface ApiResponse {
  Result: ApiResult;
  Message: string | null;
}
```

### Validation Rules (Client-side)

```typescript
export const TruckValidationRules = {
  // Required fields
  LicensePlate: {
    required: true,
    maxLength: 20,
    pattern: /^[0-9A-Z-]+$/,  // Chỉ cho phép chữ hoa, số, dấu gạch ngang
    message: 'Biển số xe không hợp lệ (tối đa 20 ký tự, chỉ chữ hoa, số, dấu gạch ngang)'
  },
  
  AgencyId: {
    required: true,
    min: 1,
    message: 'Vui lòng chọn chi nhánh'
  },
  
  UpdatedBy: {
    required: true,
    min: 1,
    message: 'Không xác định được người dùng'
  },
  
  UpdatedByUserName: {
    required: true,
    message: 'Không xác định được tên người dùng'
  },
  
  // Optional fields
  MaxPayload: {
    min: 0,
    message: 'Tải trọng phải >= 0'
  },
  
  CargoLength: {
    min: 0,
    message: 'Chiều dài phải >= 0'
  },
  
  CargoWidth: {
    min: 0,
    message: 'Chiều rộng phải >= 0'
  },
  
  CargoHeight: {
    min: 0,
    message: 'Chiều cao phải >= 0'
  },
  
  ManufacturingYear: {
    min: 1900,
    max: new Date().getFullYear() + 1,
    message: 'Năm sản xuất không hợp lệ'
  },
  
  RegistrationInfo: {
    maxLength: 200,
    message: 'Thông tin đăng kiểm tối đa 200 ký tự'
  },
  
  FuelQuotaPer100Km: {
    min: 0,
    message: 'Định mức xăng dầu phải >= 0'
  }
};
```

### Helper Functions

```typescript
// Tạo payload cho Create
export function createTruckPayload(
  formData: Partial<TruckModel>,
  currentUser: { id: number; username: string }
): TruckModel {
  return {
    Id: 0,  // Luôn = 0 khi tạo mới
    LicensePlate: formData.LicensePlate || '',
    Brand: formData.Brand ?? null,
    Model: formData.Model ?? null,
    AgencyId: formData.AgencyId || 0,
    MaxPayload: formData.MaxPayload ?? null,
    CargoLength: formData.CargoLength ?? null,
    CargoWidth: formData.CargoWidth ?? null,
    CargoHeight: formData.CargoHeight ?? null,
    OperatingStatus: formData.OperatingStatus ?? OperatingStatus.Stop,
    Status: formData.Status ?? TruckStatus.Normal,
    CurrentDriverId: formData.CurrentDriverId ?? 0,
    ManufacturingYear: formData.ManufacturingYear ?? null,
    RegistrationInfo: formData.RegistrationInfo ?? null,
    FuelQuotaPer100Km: formData.FuelQuotaPer100Km ?? null,
    UpdatedBy: currentUser.id,
    UpdatedByUserName: currentUser.username
  };
}

// Tạo payload cho Update
export function updateTruckPayload(
  truckId: number,
  formData: Partial<TruckModel>,
  currentUser: { id: number; username: string }
): TruckModel {
  // KHÔNG gửi OperatingStatus, Status, CurrentDriverId khi update
  return {
    Id: truckId,  // Phải > 0
    LicensePlate: formData.LicensePlate || '',
    Brand: formData.Brand ?? null,
    Model: formData.Model ?? null,
    AgencyId: formData.AgencyId || 0,
    MaxPayload: formData.MaxPayload ?? null,
    CargoLength: formData.CargoLength ?? null,
    CargoWidth: formData.CargoWidth ?? null,
    CargoHeight: formData.CargoHeight ?? null,
    ManufacturingYear: formData.ManufacturingYear ?? null,
    RegistrationInfo: formData.RegistrationInfo ?? null,
    FuelQuotaPer100Km: formData.FuelQuotaPer100Km ?? null,
    UpdatedBy: currentUser.id,
    UpdatedByUserName: currentUser.username
  };
}

// Error handling
export function handleApiError(response: ApiResponse): string {
  if (response.Result === ApiResult.Success) {
    return '';
  }
  
  // Map common error messages
  const errorMessage = response.Message || '';
  
  if (errorMessage.includes('UNIQUE KEY constraint') || 
      errorMessage.includes('UK_Trucks_LicensePlate')) {
    return 'Biển số xe đã tồn tại trong hệ thống';
  }
  
  if (errorMessage.includes('Không tìm thấy xe tải')) {
    return 'Xe tải không tồn tại';
  }
  
  if (errorMessage.includes('FK_Trucks_Agency')) {
    return 'Chi nhánh không tồn tại';
  }
  
  if (errorMessage.includes('Validation failed')) {
    return 'Dữ liệu không hợp lệ, vui lòng kiểm tra lại';
  }
  
  return response.Message || 'Có lỗi xảy ra, vui lòng thử lại';
}
```

### Migration Guide (Nếu đang có code cũ)

#### ⚠️ Breaking Changes

1. **Các trường đã chuyển thành nullable:**
   - `MaxPayload`, `CargoLength`, `CargoWidth`, `CargoHeight`
   - `ManufacturingYear`, `FuelQuotaPer100Km`
   - **Action**: Update TypeScript interfaces, cho phép `null` cho các trường này

2. **KHÔNG thể update trạng thái qua API này:**
   - `OperatingStatus`, `Status`, `CurrentDriverId` bị ignore khi update
   - **Action**: Remove các trường này khỏi update payload, dùng API check-in/check-out riêng

#### 📝 Checklist Migration

- [ ] Update TypeScript interfaces với nullable types
- [ ] Update form validation rules
- [ ] Remove `OperatingStatus`, `Status`, `CurrentDriverId` khỏi update form
- [ ] Sử dụng helper functions `createTruckPayload()` và `updateTruckPayload()`
- [ ] Update error handling với `handleApiError()`
- [ ] Test tạo xe tải mới với đầy đủ trường
- [ ] Test update xe tải với các trường nullable = `null`
- [ ] Verify rằng update không thay đổi `OperatingStatus`, `Status`, `CurrentDriverId`

### Example Usage (React/Angular)

```typescript
// CREATE Truck
async function createTruck(formData: Partial<TruckModel>) {
  const currentUser = getCurrentUser(); // Get from auth context
  
  const payload = createTruckPayload(formData, currentUser);
  
  const response = await fetch('/api/Truck/CreateOrUpdate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${getAccessToken()}`
    },
    body: JSON.stringify(payload)
  });
  
  const result: ApiResponse = await response.json();
  
  if (result.Result !== ApiResult.Success) {
    const errorMessage = handleApiError(result);
    throw new Error(errorMessage);
  }
  
  return result;
}

// UPDATE Truck
async function updateTruck(truckId: number, formData: Partial<TruckModel>) {
  const currentUser = getCurrentUser();
  
  const payload = updateTruckPayload(truckId, formData, currentUser);
  
  const response = await fetch('/api/Truck/CreateOrUpdate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${getAccessToken()}`
    },
    body: JSON.stringify(payload)
  });
  
  const result: ApiResponse = await response.json();
  
  if (result.Result !== ApiResult.Success) {
    const errorMessage = handleApiError(result);
    throw new Error(errorMessage);
  }
  
  return result;
}
```

### Common Pitfalls (Lỗi thường gặp)

1. ❌ **Gửi `OperatingStatus = 2`**
   - `OperatingStatus` chỉ có giá trị `0` (Stop) hoặc `1` (Running)
   - ✅ Luôn kiểm tra enum values trước khi gửi

2. ❌ **Gửi các trường nullable với giá trị empty object `{}`**
   - ✅ Dùng `null` hoặc không gửi trường đó

3. ❌ **Cố gắng update `Status` hoặc `CurrentDriverId` qua API này**
   - ✅ Dùng API check-in/check-out riêng để update các trường này

4. ❌ **Quên set `UpdatedBy` và `UpdatedByUserName`**
   - ✅ Luôn lấy từ authentication context

5. ❌ **Gửi `Id` khác 0 khi tạo mới**
   - ✅ Luôn set `Id = 0` khi create

---

---

# API Lấy Danh Sách Xe Tải Theo Chi Nhánh

## Thông tin Endpoint

**URL:** `GET /api/Truck/GetTrucksByAgency`

**Authorization:** Bearer Token - Role: `Truck.GetTrucksByAgency`

**Content-Type:** `application/json`

## Mô tả

API này dùng để **lấy danh sách tất cả xe tải** thuộc chi nhánh của người dùng hiện tại. 

### Điểm khác biệt với API khác:
- **`GetTrucksByAgency`**: Lấy **tất cả xe tải** thuộc chi nhánh (API này)
- **`GetAvailableTrucks`**: Chỉ lấy xe tải **chưa có người phụ trách**
- **`GetTruckByAgencyId`**: Yêu cầu truyền `AgencyId` trong query parameter

API này tự động xác định chi nhánh của người dùng hiện tại thông qua token authentication, không cần truyền `AgencyId`.

---

## Request

### Query Parameters

Không có query parameters. API tự động lấy thông tin chi nhánh từ người dùng hiện tại.

### Headers

```
Authorization: Bearer {access_token}
```

---

## Response

### Success Response

**Status Code:** `200 OK`

```json
{
  "Result": 0,
  "Message": null,
  "Data": [
    {
      "Id": 1,
      "LicensePlate": "29C-12345",
      "Brand": "Kia",
      "Model": "K200",
      "AgencyId": 5,
      "AgencyName": "Chi nhánh Hà Nội",
      "MaxPayload": 5.5,
      "CargoLength": 420,
      "CargoWidth": 180,
      "CargoHeight": 200,
      "OperatingStatus": 0,
      "Status": 0,
      "CurrentDriverId": 123,
      "CurrentDriverName": "Nguyễn Văn A",
      "ManufacturingYear": 2020,
      "RegistrationInfo": "Đăng kiểm đến 31/12/2026",
      "FuelQuotaPer100Km": 11.5,
      "CreatedDate": "2024-01-15T08:30:00",
      "UpdatedDate": "2024-01-20T14:45:00",
      "UpdatedBy": 456,
      "UpdatedByUserName": "admin"
    },
    {
      "Id": 2,
      "LicensePlate": "29C-67890",
      "Brand": "Hyundai",
      "Model": "Porter",
      "AgencyId": 5,
      "AgencyName": "Chi nhánh Hà Nội",
      "MaxPayload": 6.0,
      "CargoLength": 450,
      "CargoWidth": 190,
      "CargoHeight": 210,
      "OperatingStatus": 1,
      "Status": 0,
      "CurrentDriverId": 0,
      "CurrentDriverName": null,
      "ManufacturingYear": 2021,
      "RegistrationInfo": "Đăng kiểm đến 30/06/2027",
      "FuelQuotaPer100Km": 12.0,
      "CreatedDate": "2024-02-10T09:15:00",
      "UpdatedDate": "2024-02-15T16:20:00",
      "UpdatedBy": 456,
      "UpdatedByUserName": "admin"
    }
  ]
}
```

### Response Schema

| Tên trường | Kiểu dữ liệu | Mô tả |
|------------|--------------|-------|
| `Result` | `int` (enum) | `0` = Success, `2` = Failed, `3` = SystemError |
| `Message` | `string` | Thông báo lỗi (nếu có) |
| `Data` | `TruckModel[]` | Danh sách xe tải |

### TruckModel Schema

| Tên trường | Kiểu dữ liệu | Mô tả |
|------------|--------------|-------|
| `Id` | `int` | ID xe tải |
| `LicensePlate` | `string` | Biển số xe |
| `Brand` | `string?` | Thương hiệu xe tải (ví dụ: "Kia", "Hyundai") |
| `Model` | `string?` | Model xe tải (ví dụ: "K200", "Porter") |
| `AgencyId` | `int` | ID chi nhánh |
| `AgencyName` | `string` | Tên chi nhánh |
| `MaxPayload` | `decimal?` | Tải trọng tối đa (tấn) |
| `CargoLength` | `int?` | Chiều dài thùng hàng (cm) |
| `CargoWidth` | `int?` | Chiều rộng thùng hàng (cm) |
| `CargoHeight` | `int?` | Chiều cao thùng hàng (cm) |
| `OperatingStatus` | `int` (enum) | `0` = Stop, `1` = Running |
| `Status` | `int` (enum) | `0` = Normal, `1` = Broken, `2` = Maintenance, `3` = Retired |
| `CurrentDriverId` | `int` | ID tài xế hiện tại (0 = chưa có người phụ trách) |
| `CurrentDriverName` | `string?` | Tên tài xế hiện tại |
| `ManufacturingYear` | `int?` | Năm sản xuất |
| `RegistrationInfo` | `string?` | Thông tin đăng kiểm |
| `FuelQuotaPer100Km` | `decimal?` | Định mức xăng dầu (lít/100km) |
| `CreatedDate` | `DateTime` | Ngày tạo |
| `UpdatedDate` | `DateTime` | Ngày cập nhật |
| `UpdatedBy` | `int` | ID người cập nhật |
| `UpdatedByUserName` | `string` | Tên người cập nhật |

---

## Error Responses

### Người dùng không hợp lệ

**Status Code:** `400 Bad Request`

```json
"Không xác định được người dùng"
```

### Người dùng không thuộc chi nhánh nào

**Status Code:** `400 Bad Request`

```json
"Người dùng không thuộc đơn vị nào"
```

### Unauthorized

**Status Code:** `401 Unauthorized`

```json
{
  "message": "Unauthorized"
}
```

### Forbidden (Thiếu quyền)

**Status Code:** `403 Forbidden`

```json
{
  "message": "User does not have the required role: Truck.GetTrucksByAgency"
}
```

---

## Ví dụ

### Request

```bash
curl -X GET "https://api.example.com/api/Truck/GetTrucksByAgency" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```

### Response - Có xe tải

```json
{
  "Result": 0,
  "Message": null,
  "Data": [
    {
      "Id": 1,
      "LicensePlate": "29C-12345",
      "Brand": "Kia",
      "Model": "K200",
      "AgencyId": 5,
      "AgencyName": "Chi nhánh Hà Nội",
      "MaxPayload": 5.5,
      "CargoLength": 420,
      "CargoWidth": 180,
      "CargoHeight": 200,
      "OperatingStatus": 0,
      "Status": 0,
      "CurrentDriverId": 123,
      "CurrentDriverName": "Nguyễn Văn A",
      "ManufacturingYear": 2020,
      "RegistrationInfo": "Đăng kiểm đến 31/12/2026",
      "FuelQuotaPer100Km": 11.5,
      "CreatedDate": "2024-01-15T08:30:00",
      "UpdatedDate": "2024-01-20T14:45:00",
      "UpdatedBy": 456,
      "UpdatedByUserName": "admin"
    }
  ]
}
```

### Response - Không có xe tải

```json
{
  "Result": 0,
  "Message": null,
  "Data": []
}
```

---

## Frontend Integration Guide

### TypeScript Interfaces

```typescript
// Enums
export enum OperatingStatus {
  Stop = 0,
  Running = 1
}

export enum TruckStatus {
  Normal = 0,
  Broken = 1,
  Maintenance = 2,
  Retired = 3
}

export enum ApiResult {
  Success = 0,
  Failed = 2,
  SystemError = 3
}

// Response Model
export interface TruckModel {
  Id: number;
  LicensePlate: string;
  Brand: string | null;
  Model: string | null;
  AgencyId: number;
  AgencyName: string;
  MaxPayload: number | null;
  CargoLength: number | null;
  CargoWidth: number | null;
  CargoHeight: number | null;
  OperatingStatus: OperatingStatus;
  Status: TruckStatus;
  CurrentDriverId: number;
  CurrentDriverName: string | null;
  ManufacturingYear: number | null;
  RegistrationInfo: string | null;
  FuelQuotaPer100Km: number | null;
  CreatedDate: string;
  UpdatedDate: string;
  UpdatedBy: number;
  UpdatedByUserName: string;
}

export interface GetTrucksByAgencyResponse {
  Result: ApiResult;
  Message: string | null;
  Data: TruckModel[];
}
```

### Example Usage (React/Vue/Angular)

```typescript
// Fetch all trucks for current user's agency
async function getTrucksByAgency(): Promise<TruckModel[]> {
  try {
    const response = await fetch('/api/Truck/GetTrucksByAgency', {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${getAccessToken()}`,
        'Content-Type': 'application/json'
      }
    });

    if (!response.ok) {
      if (response.status === 400) {
        const errorText = await response.text();
        throw new Error(errorText);
      }
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const result: GetTrucksByAgencyResponse = await response.json();

    if (result.Result !== ApiResult.Success) {
      throw new Error(result.Message || 'Có lỗi xảy ra khi lấy danh sách xe tải');
    }

    return result.Data;
  } catch (error) {
    console.error('Error fetching trucks:', error);
    throw error;
  }
}

// Usage example
async function loadTrucks() {
  try {
    const trucks = await getTrucksByAgency();
    console.log(`Tìm thấy ${trucks.length} xe tải`);
    
    // Filter available trucks (no driver assigned)
    const availableTrucks = trucks.filter(t => t.CurrentDriverId === 0);
    console.log(`Trong đó ${availableTrucks.length} xe chưa có người phụ trách`);
    
    // Filter trucks by status
    const normalTrucks = trucks.filter(t => t.Status === TruckStatus.Normal);
    const maintenanceTrucks = trucks.filter(t => t.Status === TruckStatus.Maintenance);
    
    return {
      all: trucks,
      available: availableTrucks,
      normal: normalTrucks,
      maintenance: maintenanceTrucks
    };
  } catch (error) {
    // Handle error (show notification, etc.)
    throw error;
  }
}
```

### Vue.js Example

```vue
<template>
  <div class="truck-list">
    <h2>Danh sách xe tải chi nhánh</h2>
    
    <div v-if="loading">Đang tải...</div>
    <div v-else-if="error" class="error">{{ error }}</div>
    <div v-else-if="trucks.length === 0">Không có xe tải nào</div>
    
    <table v-else>
      <thead>
        <tr>
          <th>Biển số</th>
          <th>Hãng xe</th>
          <th>Model</th>
          <th>Tải trọng</th>
          <th>Trạng thái</th>
          <th>Tài xế</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="truck in trucks" :key="truck.Id">
          <td>{{ truck.LicensePlate }}</td>
          <td>{{ truck.Brand || '-' }}</td>
          <td>{{ truck.Model || '-' }}</td>
          <td>{{ truck.MaxPayload ? `${truck.MaxPayload} tấn` : '-' }}</td>
          <td>{{ getStatusLabel(truck.Status) }}</td>
          <td>{{ truck.CurrentDriverName || 'Chưa có' }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import type { TruckModel } from '@/types/truck';

const trucks = ref<TruckModel[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);

const getStatusLabel = (status: number): string => {
  const labels = {
    0: 'Bình thường',
    1: 'Hỏng',
    2: 'Bảo trì',
    3: 'Ngưng hoạt động'
  };
  return labels[status] || 'Không xác định';
};

const loadTrucks = async () => {
  loading.value = true;
  error.value = null;
  
  try {
    const response = await fetch('/api/Truck/GetTrucksByAgency', {
      headers: {
        'Authorization': `Bearer ${localStorage.getItem('token')}`
      }
    });
    
    if (!response.ok) {
      if (response.status === 400) {
        error.value = await response.text();
        return;
      }
      throw new Error('Không thể tải danh sách xe tải');
    }
    
    const result = await response.json();
    
    if (result.Result === 0) {
      trucks.value = result.Data;
    } else {
      error.value = result.Message || 'Có lỗi xảy ra';
    }
  } catch (err) {
    error.value = err instanceof Error ? err.message : 'Có lỗi xảy ra';
  } finally {
    loading.value = false;
  }
};

onMounted(() => {
  loadTrucks();
});
</script>
```

---

## Lưu ý quan trọng

> [!IMPORTANT]
> - API này **tự động xác định chi nhánh** từ thông tin người dùng hiện tại
> - Không cần truyền `AgencyId` trong request
> - Trả về **tất cả xe tải** của chi nhánh, bao gồm cả xe đang có người phụ trách
> - Để lấy chỉ xe chưa có người phụ trách, sử dụng API `GetAvailableTrucks`

> [!TIP]
> **Filtering client-side:**
> - Xe chưa có người phụ trách: `truck.CurrentDriverId === 0`
> - Xe đang hoạt động: `truck.OperatingStatus === 1`
> - Xe bình thường: `truck.Status === 0`
> - Kết hợp nhiều điều kiện để lọc theo nhu cầu

> [!WARNING]
> - API này yêu cầu role `Truck.GetTrucksByAgency`
> - Người dùng phải thuộc một chi nhánh hợp lệ
> - Token phải còn hiệu lực

---

## Use Cases

### 1. Hiển thị tất cả xe tải của chi nhánh
```typescript
const allTrucks = await getTrucksByAgency();
```

### 2. Lọc xe tải theo trạng thái
```typescript
const trucks = await getTrucksByAgency();

// Xe đang hoạt động bình thường
const activeTrucks = trucks.filter(t => 
  t.Status === TruckStatus.Normal && 
  t.OperatingStatus === OperatingStatus.Running
);

// Xe cần bảo trì
const needMaintenance = trucks.filter(t => 
  t.Status === TruckStatus.Maintenance
);
```

### 3. Thống kê xe tải
```typescript
const trucks = await getTrucksByAgency();

const stats = {
  total: trucks.length,
  available: trucks.filter(t => t.CurrentDriverId === 0).length,
  inUse: trucks.filter(t => t.CurrentDriverId > 0).length,
  running: trucks.filter(t => t.OperatingStatus === OperatingStatus.Running).length,
  stopped: trucks.filter(t => t.OperatingStatus === OperatingStatus.Stop).length,
  normal: trucks.filter(t => t.Status === TruckStatus.Normal).length,
  broken: trucks.filter(t => t.Status === TruckStatus.Broken).length,
  maintenance: trucks.filter(t => t.Status === TruckStatus.Maintenance).length,
  retired: trucks.filter(t => t.Status === TruckStatus.Retired).length
};
```

---

## Support

Nếu có vấn đề khi tích hợp, vui lòng liên hệ Backend team với thông tin:
- Request payload đã gửi
- Response nhận được
- Expected behavior vs Actual behavior

---

# API Cập Nhật ODO Đổ Dầu Gần Nhất

## Thông tin Endpoint

**URL:** `POST /api/Truck/UpdateLastRefillOdo`

**Authorization:** Bearer Token - Role: `Truck.UpdateLastRefillOdo`

## Mô tả

API này dùng để **cập nhật giá trị ODO đổ dầu gần nhất** của xe tải. Giá trị này được sử dụng để tính số km đã chạy cho lần đổ dầu tiếp theo.

## Request

### Query Parameters

| Tên tham số | Kiểu dữ liệu | Bắt buộc | Mô tả |
|-------------|--------------|----------|-------|
| `truckId` | `int` | Có | ID xe tải cần cập nhật (phải > 0) |
| `odoValue` | `int` | Có | Giá trị ODO (km, phải >= 0) |

## Response

### Success Response

**Status Code:** `200 OK`

```json
{
  "Result": 0,
  "Message": "Đã cập nhật ODO đổ dầu gần nhất: 15000 km"
}
```

### Error Responses

**ODO mới nhỏ hơn ODO trước đó:**
```json
{
  "Result": 2,
  "Message": "Giá trị ODO mới (12000) không thể nhỏ hơn ODO đổ dầu trước đó (15000)"
}
```

## Ví dụ

```bash
curl -X POST "https://api.example.com/api/Truck/UpdateLastRefillOdo?truckId=1&odoValue=15000" \
  -H "Authorization: Bearer {token}"
```

## Lưu ý

> [!IMPORTANT]
> - Giá trị ODO mới phải **lớn hơn hoặc bằng** ODO đổ dầu trước đó
> - Sử dụng API này sau mỗi lần đổ dầu để cập nhật ODO gần nhất
> - Công thức: `Số km đã chạy = ODO hiện tại - LastRefillOdo`

📖 **Xem tài liệu đầy đủ:** [API_Truck_UpdateLastRefillOdo.md](./API_Truck_UpdateLastRefillOdo.md)

---

# API Lấy Giá Trị ODO Đổ Dầu Gần Nhất

## Thông tin Endpoint

**URL:** `GET /api/Truck/GetLastRefillOdo`

**Authorization:** Bearer Token - Role: `Truck.GetLastRefillOdo`

## Mô tả

API này dùng để **lấy giá trị ODO đổ dầu gần nhất** của xe tải.

## Request

### Query Parameters

| Tên tham số | Kiểu dữ liệu | Bắt buộc | Mô tả |
|-------------|--------------|----------|-------|
| `truckId` | `int` | Có | ID xe tải cần lấy thông tin (phải > 0) |

## Response

### Success Response

**Status Code:** `200 OK`

```json
{
  "TruckId": 1,
  "LicensePlate": "29C-12345",
  "LastRefillOdo": 15000
}
```

### Response Schema

| Tên trường | Kiểu dữ liệu | Mô tả |
|------------|--------------|-------|
| `TruckId` | `int` | ID xe tải |
| `LicensePlate` | `string` | Biển số xe |
| `LastRefillOdo` | `int?` | Giá trị ODO đổ dầu gần nhất (null nếu chưa có) |

### Error Responses

**ID xe tải không hợp lệ:**

**Status Code:** `400 Bad Request`

```json
"ID xe tải không hợp lệ"
```

**Xe tải không tồn tại:**

**Status Code:** `404 Not Found`

```json
"Không tìm thấy xe tải"
```

## Ví dụ

### Request

```bash
curl -X GET "https://api.example.com/api/Truck/GetLastRefillOdo?truckId=1" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```

### Response - Có giá trị ODO

```json
{
  "TruckId": 1,
  "LicensePlate": "29C-12345",
  "LastRefillOdo": 15000
}
```

### Response - Chưa có giá trị ODO

```json
{
  "TruckId": 1,
  "LicensePlate": "29C-12345",
  "LastRefillOdo": null
}
```

## Frontend Integration

### TypeScript Interface

```typescript
export interface LastRefillOdoResponse {
  TruckId: number;
  LicensePlate: string;
  LastRefillOdo: number | null;
}
```

### Example Usage

```typescript
async function getLastRefillOdo(truckId: number): Promise<LastRefillOdoResponse> {
  const response = await fetch(
    `/api/Truck/GetLastRefillOdo?truckId=${truckId}`,
    {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${getAccessToken()}`
      }
    }
  );

  if (!response.ok) {
    if (response.status === 404) {
      throw new Error('Không tìm thấy xe tải');
    }
    throw new Error('Lỗi khi lấy thông tin ODO');
  }

  return await response.json();
}

// Sử dụng
const odoInfo = await getLastRefillOdo(1);
console.log(`Xe ${odoInfo.LicensePlate}: ODO = ${odoInfo.LastRefillOdo ?? 'Chưa có'} km`);
```

## Lưu ý

> [!TIP]
> - Sử dụng API này để kiểm tra giá trị ODO hiện tại trước khi cập nhật
> - Giá trị `LastRefillOdo` có thể là `null` nếu chưa từng cập nhật
> - API này yêu cầu role `Truck.GetLastRefillOdo`

