# API Documentation - Fuel Card Management

## Mục lục
1. [Quản lý Thẻ Xăng Dầu](#1-quản-lý-thẻ-xăng-dầu)
2. [Quản lý Giao dịch Thẻ](#2-quản-lý-giao-dịch-thẻ)
3. [Quản lý Đổ Dầu](#3-quản-lý-đổ-dầu)
4. [Quản lý Số Dư Dầu Khoán](#4-quản-lý-số-dư-dầu-khoán)
5. [Quản lý Thanh Toán Tiền Dầu](#5-quản-lý-thanh-toán-tiền-dầu)
6. [Models & Enums](#6-models--enums)

---

## 1. Quản lý Thẻ Xăng Dầu

### 1.1. Tìm kiếm thẻ xăng dầu

**Endpoint:** `POST /api/FuelCard/Search`

**Authorization:** `FuelCard.Search`

**Request Body:**
```json
{
  "Pagination": {
    "PageIndex": 1,
    "PageSize": 15
  },
  "Search": {
    "PredicateObject": {
      "Keyword": "5234"
    }
  },
  "Sort": {
    "Predicate": "CardNumber",
    "Reverse": false
  }
}
```

**Response:**
```json
{
  "Items": [
    {
      "Id": 1,
      "CardNumber": "52347217____5261",
      "CardholderName": "NGUYEN VAN A",
      "BankName": "Vietcombank",
      "CardType": "Credit Card",
      "TruckId": 10,
      "TruckLicensePlate": "29C-12345",
      "ResponsibleUserId": 50,
      "ResponsibleUserName": "driver01",
      "Status": 1,
      "StatusStr": "Đang hoạt động",
      "ExpiryDate": "2026-12-31T00:00:00+07:00",
      "CreditLimit": 50000000,
      "CurrentDebt": 5500000,
      "Notes": "",
      "CreatedTime": "2025-01-15T10:30:00+07:00",
      "UpdatedTime": "2025-01-20T14:20:00+07:00"
    }
  ],
  "TotalRecord": 1,
  "NumberOfPages": 1
}
```

**Sort Options:**
- `CardNumber` - Theo số thẻ
- `Status` - Theo trạng thái
- `CurrentDebt` - Theo số nợ hiện tại

---

### 1.2. Lấy thông tin thẻ theo ID

**Endpoint:** `GET /api/FuelCard/GetById/{id}`

**Authorization:** `FuelCard.GetById`

**Response:**
```json
{
  "Id": 1,
  "CardNumber": "52347217____5261",
  "CardholderName": "NGUYEN VAN A",
  "BankName": "Vietcombank",
  "CardType": "Credit Card",
  "TruckId": 10,
  "TruckLicensePlate": "29C-12345",
  "ResponsibleUserId": 50,
  "ResponsibleUserName": "driver01",
  "Status": 1,
  "StatusStr": "Đang hoạt động",
  "ExpiryDate": "2026-12-31T00:00:00+07:00",
  "CreditLimit": 50000000,
  "CurrentDebt": 5500000,
  "Notes": "Thẻ dành cho xe container",
  "CreatedTime": "2025-01-15T10:30:00+07:00",
  "UpdatedTime": "2025-01-20T14:20:00+07:00"
}
```

---

### 1.3. Tạo hoặc cập nhật thẻ

**Endpoint:** `POST /api/FuelCard/CreateOrUpdate`

**Authorization:** `FuelCard.CreateOrUpdate`

**Request Body (Create):**
```json
{
  "Id": 0,
  "CardNumber": "52347217____5261",
  "CardholderName": "NGUYEN VAN A",
  "BankName": "Vietcombank",
  "CardType": "Credit Card",
  "TruckId": 10,
  "ResponsibleUserId": 50,
  "Status": 1,
  "ExpiryDate": "2026-12-31T00:00:00+07:00",
  "CreditLimit": 50000000,
  "Notes": "Thẻ dành cho xe container"
}
```

**Request Body (Update):**
```json
{
  "Id": 1,
  "CardNumber": "52347217____5261",
  "CardholderName": "NGUYEN VAN B",
  "BankName": "Vietcombank",
  "CardType": "Credit Card",
  "TruckId": 10,
  "ResponsibleUserId": 51,
  "Status": 1,
  "ExpiryDate": "2027-12-31T00:00:00+07:00",
  "CreditLimit": 60000000,
  "Notes": "Đã cập nhật người giữ thẻ"
}
```

**Request Parameters:**
- `Id` (int, required): ID thẻ (0 = tạo mới, >0 = cập nhật)
- `CardNumber` (string, required, max 50): Số thẻ (có thể che một phần: 52347217____5261)
- `CardholderName` (string, max 256): Tên chủ thẻ
- `BankName` (string, max 256): Tên ngân hàng phát hành
- `CardType` (string, max 50): Loại thẻ (Visa, MasterCard, JCB...)
- `ResponsibleUserId` (int?, optional): ID người phụ trách thẻ (có thể là bất kỳ ai trong công ty)
- `Status` (int, required): Trạng thái thẻ (1=Active, 2=Inactive, 3=Expired, 4=Blocked)
- `ExpiryDate` (DateTimeOffset?, optional): Ngày hết hạn thẻ
- `CreditLimit` (decimal?, optional): Hạn mức tín dụng
- `Notes` (string, max 1024): Ghi chú

> [!NOTE]
> **ResponsibleUserId - Người phụ trách thẻ**
> - Trường này là **optional** (nullable)
> - Có thể gán cho **bất kỳ user nào** trong công ty (không chỉ lái xe)
> - Use case: Quản lý ai đang giữ/quản lý thẻ xăng dầu
> - Nếu không truyền hoặc truyền `null`: Thẻ không có người phụ trách cụ thể

**Response:**
```json
{
  "Result": 1,
  "Message": "Tạo thẻ xăng dầu thành công"
}
```

**Response (Update):**
```json
{
  "Result": 1,
  "Message": "Cập nhật thẻ xăng dầu thành công"
```

---

### 1.4. Xóa thẻ

**Endpoint:** `POST /api/FuelCard/Delete/{id}`

**Authorization:** `FuelCard.Delete`

**Response:**
```json
{
  "Result": 1,
  "Message": "Xóa thẻ xăng dầu thành công"
}
```

**Note:** Không thể xóa thẻ đã có giao dịch.

---

### 1.5. Gán thẻ cho xe

**Endpoint:** `POST /api/FuelCard/AssignToTruck?fuelCardId={fuelCardId}&truckId={truckId}`

**Authorization:** `FuelCard.AssignToTruck`

**Query Parameters:**
- `fuelCardId` (int): ID thẻ xăng dầu
- `truckId` (int): ID xe tải

**Response:**
```json
{
  "Result": 1,
  "Message": "Gán thẻ cho xe thành công"
}
```

---

### 1.6. Lấy danh sách thẻ đang hoạt động

**Endpoint:** `GET /api/FuelCard/GetActiveCards`

**Authorization:** `FuelCard.GetActiveCards`

**Response:**
```json
[
  {
    "Id": 1,
    "CardNumber": "52347217____5261",
    "CardholderName": "NGUYEN VAN A",
    "BankName": "Vietcombank",
    "CardType": "Credit Card",
    "Status": 1,
    "TruckLicensePlate": "29C-12345"
  },
  {
    "Id": 2,
    "CardNumber": "52347217____5262",
    "CardholderName": "TRAN VAN B",
    "BankName": "Vietcombank",
    "CardType": "Credit Card",
    "Status": 1,
    "TruckLicensePlate": "29C-54321"
  }
]
```

---

### 1.7. Lấy tổng nợ thẻ tín dụng

**Endpoint:** `GET /api/FuelCard/GetTotalDebt`

**Authorization:** `FuelCard.GetTotalDebt`

**Response:**
```json
{
  "TotalDebt": 15750000
}
```

**Use Case:** Dùng cho báo cáo tài chính - Tổng nợ thẻ tín dụng (khác với tài khoản ngân hàng).

---

## 2. Quản lý Giao dịch Thẻ

### 2.1. Import sao kê thẻ từ Excel

**Endpoint:** `POST /api/FuelCard/ImportStatement`

**Authorization:** `FuelCard.ImportStatement`

**Request (Form-Data):**
- `FuelCardId` (int): ID thẻ xăng dầu
- `File` (IFormFile): File Excel sao kê
- `StatementPeriodFrom` (DateTimeOffset?, optional): Từ ngày
- `StatementPeriodTo` (DateTimeOffset?, optional): Đến ngày

**Excel Format Example:**
```
Ngày giao dịch    | Tài khoản thẻ      | Số tiền      | Loại tiền | Loại giao dịch | Địa điểm                          | Tình trạng
01/12/2025 09:38  | 52347217____5261   | -1,119,109   | VND       | Retail         | PLX_HAI PHONG_CH 30 - HAI PHONG  | Thành công
```

**Response:**
```json
{
  "Result": 2,
  "Message": "Chức năng import sao kê đang được phát triển"
}
```

**TODO:** Chức năng này chưa được implement đầy đủ.

---

### 2.1.1. Import lịch sử giao dịch thẻ từ Excel ⭐ **New Feature**

**Endpoint:** `POST /api/FuelCard/ImportTransactionHistory`

**Authorization:** `FuelCard.ImportTransactionHistory`

**Mô tả:** Import lịch sử giao dịch thẻ xăng dầu từ file Excel. Hệ thống tự động tìm thẻ dựa vào số thẻ trong file và tạo giao dịch tương ứng.

**Request (Form-Data):**
- `File` (IFormFile, required): File Excel chứa dữ liệu giao dịch
- `OverwriteExisting` (bool, optional): Ghi đè giao dịch đã tồn tại (mặc định: false)
- `FromDate` (DateTimeOffset?, optional): Chỉ import giao dịch từ ngày
- `ToDate` (DateTimeOffset?, optional): Chỉ import giao dịch đến ngày

**Excel Format Requirements:**
- **Dòng 1**: Header (tiêu đề các cột)
- **Dòng 2 trở đi**: Dữ liệu giao dịch

**Các cột được hỗ trợ:**
| Tên cột (Header)                   | Bắt buộc | Mô tả                                          |
| ---------------------------------- | -------- | ---------------------------------------------- |
| Tài khoản thẻ / Số thẻ             | ✅        | Số thẻ tín dụng để mapping với thẻ trong hệ thống |
| Ngày giao dịch                     | ✅        | Định dạng: dd/MM/yyyy hoặc dd/MM/yyyy HH:mm    |
| Số tiền                            | ✅        | Số tiền giao dịch (hỗ trợ: 1.234.567 hoặc -1,234,567) |
| Loại tiền                          |          | VND, USD... (mặc định: VND)                    |
| Loại giao dịch                     |          | Retail, Refund, Payment...                     |
| Địa điểm                           |          | Nơi giao dịch                                  |
| Tình trạng                         |          | Thành công, Chờ xử lý, Thất bại...             |

**Excel Format Example:**
```
Ngày giao dịch    | Tài khoản thẻ/ Số thẻ | Số tiền      | Loại tiền | Loại giao dịch | Địa điểm                          | Tình trạng
01/12/2025 09:38  | 52347217____5261       | -1,119,109   | VND       | Retail         | PLX_HAI PHONG_CH 30 - HAI PHONG  | Thành công
02/12/2025 14:22  | 52347217____5262       | -850,000     | VND       | Retail         | PVOIL Station - HCM              | Thành công
```

**Response Success:**
```json
{
  "Result": 1,
  "Message": "Import thành công: 45/50 giao dịch. Có 2 số thẻ không tìm thấy trong hệ thống.",
  "TotalRows": 50,
  "ProcessedRows": 45,
  "ErrorRows": 0,
  "SkippedRows": 3,
  "DuplicateRows": 2,
  "UnknownCardsCount": 2,
  "UnknownCards": [
    "52347217____9999",
    "52347217____8888"
  ],
  "TotalAmountImported": 25500000,
  "Warnings": [
    "Dòng 5: Không tìm thấy thẻ '52347217____9999' trong hệ thống",
    "Dòng 12: Không tìm thấy thẻ '52347217____8888' trong hệ thống"
  ],
  "ErrorMessages": []
}
```

**Response Error:**
```json
{
  "Result": 2,
  "Message": "Không tìm thấy cột 'Tài khoản thẻ' hoặc 'Số thẻ' trong file Excel",
  "TotalRows": 0,
  "ProcessedRows": 0,
  "ErrorRows": 0,
  "SkippedRows": 0,
  "DuplicateRows": 0,
  "UnknownCardsCount": 0,
  "UnknownCards": [],
  "TotalAmountImported": 0,
  "Warnings": [],
  "ErrorMessages": []
}
```

**Processing Logic:**
1. Đọc file Excel và tìm các cột dựa trên header
2. Với mỗi dòng dữ liệu:
   - Tìm thẻ xăng dầu dựa trên số thẻ (hỗ trợ partial match, masked numbers)
   - Parse ngày giao dịch (hỗ trợ nhiều định dạng VN)
   - Parse số tiền (hỗ trợ định dạng 1.234.567 và 1,234,567)
   - Kiểm tra trùng lặp (theo FuelCardId + Date + RawAmount)
   - Tạo/cập nhật FuelCardTransaction
3. Cập nhật CurrentDebt cho các thẻ có giao dịch mới

**Card Number Matching:**
- Exact match: So khớp chính xác sau khi chuẩn hóa (loại bỏ spaces, dashes, *)
- Partial match: Matching based on last 4 digits
- Masked match: Support for `5234****5261` format

> [!IMPORTANT]
> **Duplicate Detection**
> - Giao dịch được coi là trùng nếu: cùng FuelCardId + cùng ngày + cùng số tiền gốc (RawAmount)
> - Nếu `OverwriteExisting = false`: Bỏ qua giao dịch trùng
> - Nếu `OverwriteExisting = true`: Cập nhật thông tin giao dịch trùng

> [!TIP]
> **Best Practices**
> - Đảm bảo số thẻ trong Excel khớp với số thẻ trong hệ thống
> - Sử dụng `OverwriteExisting = true` cẩn thận - sẽ ghi đè dữ liệu cũ
> - Kiểm tra danh sách `UnknownCards` để thêm thẻ còn thiếu vào hệ thống

---

### 2.2. Tìm kiếm giao dịch thẻ

**Endpoint:** `POST /api/FuelCard/SearchTransactions?fuelCardId={fuelCardId}`

**Authorization:** `FuelCard.SearchTransactions`

**Query Parameters:**
- `fuelCardId` (int?, optional): Lọc theo thẻ

**Request Body:**
```json
{
  "Pagination": {
    "PageIndex": 1,
    "PageSize": 20
  },
  "Search": {
    "PredicateObject": {
      "Keyword": "PLX HAI PHONG",
      "TransactionStart": "2025-01-01T00:00:00+07:00",
      "TransactionEnd": "2025-01-31T23:59:59+07:00",
      "HasRefill": true
    }
  },
  "Sort": {
    "Predicate": "TransactionDate",
    "Reverse": true
  }
}
```

**Search Options:**
- `Keyword` (string, optional): Tìm theo số thẻ hoặc địa điểm giao dịch
- `TransactionStart` (DateTimeOffset, optional): Lọc giao dịch từ ngày (bắt đầu từ 00:00:00 của ngày)
- `TransactionEnd` (DateTimeOffset, optional): Lọc giao dịch đến ngày (kết thúc lúc 23:59:59 của ngày)
- `HasRefill` (bool?, optional): Lọc theo trạng thái đã đổ dầu
  - `true`: Chỉ lấy giao dịch đã có đổ dầu (FuelRefillId > 0)
  - `false`: Chỉ lấy giao dịch chưa có đổ dầu (FuelRefillId == null hoặc == 0)
  - `null` hoặc không truyền: Lấy tất cả

> [!NOTE]
> **Lọc theo thời gian giao dịch**
> - Cả `TransactionStart` và `TransactionEnd` phải được truyền cùng nhau
> - Hệ thống tự động áp dụng `StartOfDay()` cho `TransactionStart` và `EndOfDay()` cho `TransactionEnd`
> - Điều này đảm bảo lấy đầy đủ giao dịch trong khoảng thời gian được chọn

**Response:**
```json
{
  "Items": [
    {
      "Id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "FuelCardId": 1,
      "CardNumber": "52347217____5261",
      "TransactionDate": "2025-01-12T09:38:23+07:00",
      "Amount": 1119109,
      "RawAmount": -1119109,
      "Currency": "VND",
      "TransactionType": "Retail",
      "Location": "PLX_HAI PHONG_CH 30 - HAI PHONG - VNM;Vietnam",
      "Status": 1,
      "StatusStr": "Thành công",
      "FuelRefillId": 100,
      "IsReconciled": true,
      "IsPaidToBank": false,
      "PaidToBank": null,
      "ProcessNote": "",
      "HasRefill": true,
      "CreatedTime": "2025-01-12T10:00:00+07:00"
    }
  ],
  "TotalRecord": 1,
  "NumberOfPages": 1
}
```

**Response Fields:**
- `HasRefill` (bool): Giao dịch đã được đổ dầu hay chưa (true nếu có FuelRefillId)

---

### 2.3. Lấy chi tiết giao dịch

**Endpoint:** `GET /api/FuelCard/GetTransactionById/{id}`

**Authorization:** `FuelCard.GetTransactionById`

**Response:** Giống như item trong SearchTransactions

---

### 2.4. Đối soát giao dịch với lần đổ dầu

**Endpoint:** `POST /api/FuelCard/ReconcileTransaction?transactionId={transactionId}&fuelRefillId={fuelRefillId}`

**Authorization:** `FuelCard.ReconcileTransaction`

**Query Parameters:**
- `transactionId` (Guid): ID giao dịch thẻ
- `fuelRefillId` (int): ID lần đổ dầu

**Response:**
```json
{
  "Result": 1,
  "Message": "Đối soát thành công"
}
```

**Use Case:** Liên kết giao dịch thẻ từ sao kê với lần đổ dầu thực tế của lái xe.

---

### 2.5. Đánh dấu đã thanh toán cho ngân hàng

**Endpoint:** `POST /api/FuelCard/MarkTransactionPaid/{id}`

**Authorization:** `FuelCard.MarkTransactionPaid`

**Response:**
```json
{
  "Result": 1,
  "Message": "Đánh dấu đã thanh toán thành công"
}
```

**Side Effect:** 
- Cập nhật `IsPaidToBank = true`
- Cập nhật `PaidToBank = now()`
- Giảm `FuelCard.CurrentDebt`

---

### 2.6. Lấy giao dịch chưa đối soát theo thẻ và ngày

**Endpoint:** `GET /api/FuelCard/GetUnreconciledTransactions?fuelCardId={fuelCardId}&date={date}`

**Authorization:** `FuelCard.GetUnreconciledTransactions`

**Query Parameters:**
- `fuelCardId` (int, required): ID của thẻ xăng dầu
- `date` (DateTimeOffset, required): Ngày giao dịch cần lọc

**Example Request:**
```
GET /api/FuelCard/GetUnreconciledTransactions?fuelCardId=1&date=2025-01-30T00:00:00+07:00
```

**Response (Success):**
```json
[
  {
    "Id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "FuelCardId": 1,
    "CardNumber": "52347217____5261",
    "TransactionDate": "2025-01-30T09:38:23+07:00",
    "Amount": 1119109,
    "RawAmount": -1119109,
    "Currency": "VND",
    "TransactionType": "Retail",
    "Location": "PLX_HAI PHONG_CH 30 - HAI PHONG - VNM;Vietnam",
    "Status": 1,
    "StatusStr": "Thành công",
    "CreatedTime": "2025-01-30T10:00:00+07:00"
  }
]
```

**Response (No Unreconciled Transactions):**
```json
[]
```

**Response (Invalid FuelCardId):**
```
"FuelCardId không hợp lệ"
```

**Use Case:** Lấy danh sách các giao dịch thẻ trong ngày chưa được đối soát với lần đổ dầu (chưa có `FuelRefillId`). API này hữu ích khi cần tìm giao dịch để đối soát với lần đổ dầu mới tạo.

**Logic:**
- Lọc giao dịch theo `FuelCardId`
- Lọc giao dịch trong ngày được chỉ định (từ 00:00:00 đến 23:59:59)
- Chỉ lấy giao dịch chưa đối soát (`FuelRefillId == null`)
- Sắp xếp theo `TransactionDate` tăng dần

---

## 3. Quản lý Đổ Dầu

### 3.1. Tạo hoặc cập nhật thông tin đổ dầu ⭐ **Core Feature**

**Endpoint:** `POST /api/FuelCard/CreateOrUpdateRefill`

**Authorization:** `FuelCard.CreateOrUpdateRefill`

**Request (Form-Data):**
- `Id` (int, required): ID bản ghi đổ dầu (0 = tạo mới, >0 = cập nhật)
- `TruckId` (int, required): ID xe tải
- `DriverId` (int?, optional): ID lái xe thực hiện đổ dầu (mặc định là người dùng hiện tại)
- `FuelCardId` (int?, optional): ID thẻ xăng dầu (null nếu trả tiền mặt)
- `FuelCardTransactionId` (Guid?, optional): ID giao dịch thẻ tín dụng cần đối soát (nếu có, hệ thống sẽ tự động liên kết)
- `CurrentOdo` (int, required): Số ODO hiện tại
- `PreviousOdo` (int, required): Số ODO trước đó
- `Amount` (decimal, required): Số tiền đổ dầu (VD: 500000)
- `UnitPrice` (decimal, required): Đơn giá dầu (VD: 20000)
- `PaymentMethod` (int, required): 1=Card, 2=Cash
- `StationName` (string, optional): Tên cây xăng
- `StationAddress` (string, optional): Địa chỉ cây xăng
- `RefillTime` (DateTimeOffset, required): Thời gian đổ dầu
- `Notes` (string, optional): Ghi chú
- `Files` (IFormFile[], required): Danh sách hình ảnh (ít nhất 1 ảnh)
- `MediaTypes` (int[], required): Loại ảnh tương ứng

**MediaTypes Values:**
- `10` - OdoBeforePhoto (Ảnh đồng hồ ODO trước khi đổ dầu)
- `11` - OdoAfterPhoto (Ảnh đồng hồ ODO sau khi đổ dầu)
- `12` - FuelPumpPhoto (Ảnh cây dầu - hiển thị số tiền, số lít, đơn giá)
- `13` - FuelReceiptPhoto (Ảnh hóa đơn đổ dầu)

**Example Request 1 - Tạo mới đổ dầu cho chính mình:**
```
POST /api/FuelCard/CreateOrUpdateRefill
Content-Type: multipart/form-data

Id: 0
TruckId: 10
CurrentOdo: 45300
PreviousOdo: 45000
Amount: 500000
UnitPrice: 20000
PaymentMethod: 1
StationName: Petrolimex Hải Phòng
StationAddress: 30 Lạch Tray, Hải Phòng
RefillTime: 2025-01-23T08:30:00+07:00
Notes: Đổ đầy bình
Files: [file1.jpg, file2.jpg, file3.jpg, file4.jpg]
MediaTypes: [10, 11, 12, 13]
```

**Example Request 2 - Tạo mới đổ dầu cho lái xe khác:**
```
POST /api/FuelCard/CreateOrUpdateRefill
Content-Type: multipart/form-data

Id: 0
TruckId: 10
DriverId: 456
FuelCardId: 1
CurrentOdo: 45300
PreviousOdo: 45000
Amount: 500000
UnitPrice: 20000
PaymentMethod: 1
StationName: Petrolimex Hải Phòng
StationAddress: 30 Lạch Tray, Hải Phòng
RefillTime: 2025-01-23T08:30:00+07:00
Notes: Nhập đổ dầu cho lái xe Nguyễn Văn B
Files: [file1.jpg, file2.jpg, file3.jpg, file4.jpg]
MediaTypes: [10, 11, 12, 13]
```

**Example Request 3 - Cập nhật chỉ thông tin (không đổi hình ảnh):**
```
POST /api/FuelCard/CreateOrUpdateRefill
Content-Type: multipart/form-data

Id: 100
TruckId: 10
CurrentOdo: 45350
PreviousOdo: 45000
Amount: 550000
UnitPrice: 20000
PaymentMethod: 1
StationName: Petrolimex Hải Phòng
StationAddress: 30 Lạch Tray, Hải Phòng
RefillTime: 2025-01-23T08:30:00+07:00
Notes: Cập nhật số ODO và số tiền
```

**Example Request 4 - Cập nhật có chỉnh sửa hình ảnh (tối ưu):**
```
POST /api/FuelCard/CreateOrUpdateRefill
Content-Type: multipart/form-data

Id: 100
TruckId: 10
CurrentOdo: 45350
PreviousOdo: 45000
Amount: 550000
UnitPrice: 20000
PaymentMethod: 1
StationName: Petrolimex Hải Phòng
StationAddress: 30 Lạch Tray, Hải Phòng
RefillTime: 2025-01-23T08:30:00+07:00
Notes: Cập nhật và thay ảnh thứ 3
ExistingMediaIds: [1, 2, 0, 4]  // Giữ media ID 1,2,4 và upload ảnh mới cho vị trí 3
Files: [new_fuel_pump.jpg]       // Chỉ upload 1 ảnh mới
MediaTypes: [12]                  // Loại ảnh tương ứng
```

**Response (Tạo mới):**
```json
{
  "Result": 1,
  "Message": "Đổ dầu thành công. Chênh lệch: 8.00 lít (được hưởng)"
}
```

**Response (Cập nhật):**
```json
{
  "Result": 1,
  "Message": "Cập nhật thông tin đổ dầu thành công"
}
```

**Calculation Logic:**
```
1. PreviousOdo = 45000 (từ request)
2. DistanceKm = CurrentOdo - PreviousOdo = 45300 - 45000 = 300 km
3. ActualLiters = Amount / UnitPrice = 500000 / 20000 = 25 lít
4. FuelQuotaPer100Km = 11 lít (lấy từ Truck)
5. QuotaLiters = (DistanceKm × FuelQuotaPer100Km) / 100 = (300 × 11) / 100 = 33 lít
6. DifferenceLiters = QuotaLiters - ActualLiters = 33 - 25 = 8 lít (lái xe được hưởng)
```

**Side Effects (Tạo mới):**
- Tạo `FuelRefill` với các giá trị tính toán và DriverId
- Upload và lưu media vào `FuelRefillMedia`
- Cập nhật `Truck.LastRefillOdo` = CurrentOdo
- **Đối soát giao dịch thẻ** (nếu có `FuelCardTransactionId`):
  - Validate giao dịch thuộc cùng `FuelCardId`
  - Validate giao dịch chưa được đối soát
  - Cập nhật `FuelRefill.FuelCardTransactionId` = transaction ID
  - Cập nhật `FuelCardTransaction.FuelRefillId` = refill ID
  - Cập nhật `FuelCardTransaction.IsReconciled` = true

**Side Effects (Cập nhật):**
- Cập nhật thông tin `FuelRefill` với các giá trị mới
- **Tối ưu media update**: Chỉ xóa media không còn dùng và upload media mới (dựa vào `ExistingMediaIds`)
- Cập nhật `DisplayOrder` cho các media được giữ lại
- Cập nhật `Truck.LastRefillOdo` = CurrentOdo nếu đây là lần đổ dầu mới nhất
- **Xử lý thay đổi đối soát** (nếu `FuelCardTransactionId` thay đổi):
  - Xóa đối soát cũ: Set `FuelCardTransaction.FuelRefillId = null`, `IsReconciled = false`
  - Thêm đối soát mới: Validate và cập nhật hai chiều như khi tạo mới
  - Set null: Xóa đối soát hiện tại

> [!IMPORTANT]
> **Tính năng Tạo hoặc Cập nhật**
> - API này thay thế cho cả `RefillFuel` (tạo mới) và `UpdateRefill` (cập nhật)
> - Sử dụng `Id = 0` để tạo mới, `Id > 0` để cập nhật
> - Khi cập nhật, tất cả các trường đều phải được gửi lại (không hỗ trợ partial update)

> [!NOTE]
> **DriverId Parameter**
> - Nếu **không truyền** `DriverId`: Hệ thống tự động sử dụng người dùng hiện tại đăng nhập
> - Nếu **có truyền** `DriverId`: Cho phép nhập đổ dầu cho lái xe khác (use case: quản lý nhập liệu thay cho lái xe)

> [!NOTE]
> **PreviousOdo Parameter**
> - Trường này là **bắt buộc** trong API mới
> - Frontend cần tự động lấy từ `Truck.LastRefillOdo` hoặc cho phép người dùng nhập thủ công

> [!NOTE]
> **FuelCardTransactionId - Đối soát tự động**
> - **Tạo mới**: Truyền `FuelCardTransactionId` để tự động đối soát giao dịch thẻ với lần đổ dầu
> - **Cập nhật**:
>   - Truyền ID mới để thay đổi đối soát
>   - Truyền `null` để xóa đối soát
>   - Không truyền để giữ nguyên
> - **Validation**:
>   - Giao dịch phải thuộc cùng `FuelCardId` với đổ dầu
>   - Giao dịch chưa được đối soát với lần đổ dầu khác
> - **Use case**: Sử dụng API `GetUnreconciledTransactions` để lấy danh sách giao dịch chưa đối soát, sau đó chọn giao dịch phù hợp khi tạo/cập nhật đổ dầu

> [!TIP]
> **ExistingMediaIds - Tối ưu cập nhật hình ảnh**
> - Khi **tạo mới** (`Id = 0`): Không cần truyền `ExistingMediaIds`
> - Khi **cập nhật** (`Id > 0`):
>   - **Không đổi ảnh**: Không cần truyền `Files`, `MediaTypes`, `ExistingMediaIds`
>   - **Đổi một số ảnh**: Truyền `ExistingMediaIds` với ID của ảnh giữ lại và `0` cho vị trí cần upload mới
>   - **Ví dụ**: `[1, 2, 0, 4]` = Giữ media ID 1,2,4 và upload ảnh mới cho vị trí thứ 3
>   - Chỉ cần upload file cho các vị trí có ID = 0 trong `Files` array
> - **Lợi ích**: Tiết kiệm băng thông, không cần upload lại toàn bộ ảnh khi chỉ thay đổi 1-2 ảnh


---

### 3.2. Tìm kiếm lịch sử đổ dầu

**Endpoint:** `POST /api/FuelCard/SearchRefills`

**Authorization:** `FuelCard.SearchRefills`

**Request Body:**
```json
{
  "Pagination": {
    "PageIndex": 1,
    "PageSize": 15
  },
  "Search": {
    "PredicateObject": {
      "TruckId": 10,
      "Keyword": "29C-12345"
    }
  },
  "Sort": {
    "Predicate": "RefillTime",
    "Reverse": true
  }
}
```

**Search Options:**
- `TruckId` (int, optional): Lọc theo xe tải
- `Keyword` (string, optional): Tìm theo biển số xe, tên lái xe, hoặc tên cây xăng

**Response:**
```json
{
  "Items": [
    {
      "Id": 100,
      "TruckActivityId": 0,
      "TruckId": 10,
      "TruckLicensePlate": "29C-12345",
      "DriverId": 50,
      "DriverName": "driver01",
      "DriverFullName": "Nguyễn Văn A",
      "FuelCardId": 1,
      "FuelCardNumber": "52347217____5261",
      "PreviousOdo": 45000,
      "CurrentOdo": 45300,
      "DistanceKm": 300,
      "Amount": 500000,
      "UnitPrice": 20000,
      "ActualLiters": 25,
      "FuelQuotaPer100Km": 11,
      "QuotaLiters": 33,
      "DifferenceLiters": 8,
      "PaymentMethod": 1,
      "PaymentMethodStr": "Thẻ tín dụng",
      "StationName": "Petrolimex Hải Phòng",
      "StationAddress": "30 Lạch Tray, Hải Phòng",
      "RefillTime": "2025-01-23T08:30:00+07:00",
      "FuelCardTransactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "Notes": "Đổ đầy bình",
      "CreatedTime": "2025-01-23T08:35:00+07:00"
    }
  ],
  "TotalRecord": 1,
  "NumberOfPages": 1
}
```

> [!NOTE]
> **MediaItems trong SearchRefills**
> - API `SearchRefills` **không trả về** danh sách hình ảnh (`MediaItems`) để tối ưu hiệu suất
> - Để lấy hình ảnh, sử dụng API `GetRefillById` với ID cụ thể

> [!NOTE]
> **TruckActivityId Field**
> - Field `TruckActivityId` vẫn tồn tại trong response để backward compatibility
> - Giá trị luôn = 0 (deprecated, không còn sử dụng)
> - Sẽ được loại bỏ hoàn toàn trong phiên bản tương lai

**Sort Options:**
- `RefillTime` - Theo thời gian đổ
- `DifferenceLiters` - Theo chênh lệch

---

### 3.3. Lấy chi tiết lần đổ dầu

**Endpoint:** `GET /api/FuelCard/GetRefillById/{id}`

**Authorization:** `FuelCard.GetRefillById`

**Response:**
```json
{
  "Id": 100,
  "TruckActivityId": 0,
  "TruckId": 10,
  "TruckLicensePlate": "29C-12345",
  "DriverId": 50,
  "DriverName": "driver01",
  "DriverFullName": "Nguyễn Văn A",
  "FuelCardId": 1,
  "FuelCardNumber": "52347217____5261",
  "PreviousOdo": 45000,
  "CurrentOdo": 45300,
  "DistanceKm": 300,
  "Amount": 500000,
  "UnitPrice": 20000,
  "ActualLiters": 25,
  "FuelQuotaPer100Km": 11,
  "QuotaLiters": 33,
  "DifferenceLiters": 8,
  "PaymentMethod": 1,
  "PaymentMethodStr": "Thẻ tín dụng",
  "StationName": "Petrolimex Hải Phòng",
  "StationAddress": "30 Lạch Tray, Hải Phòng",
  "RefillTime": "2025-01-23T08:30:00+07:00",
  "FuelCardTransactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "Notes": "Đổ đầy bình",
  "CreatedTime": "2025-01-23T08:35:00+07:00",
  "MediaItems": [
    {
      "Id": 1,
      "TruckActivityId": 0,
      "MediaId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "MediaUrl": "https://example.com/media/fuel-refill/odo-before.jpg",
      "AltText": "Ảnh đổ dầu - Ảnh đồng hồ ODO trước khi đổ dầu",
      "Caption": "Ảnh đổ dầu - Ảnh đồng hồ ODO trước khi đổ dầu",
      "MediaType": 10,
      "DisplayOrder": 1
    },
    {
      "Id": 2,
      "TruckActivityId": 0,
      "MediaId": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
      "MediaUrl": "https://example.com/media/fuel-refill/odo-after.jpg",
      "AltText": "Ảnh đổ dầu - Ảnh đồng hồ ODO sau khi đổ dầu",
      "Caption": "Ảnh đổ dầu - Ảnh đồng hồ ODO sau khi đổ dầu",
      "MediaType": 11,
      "DisplayOrder": 2
    },
    {
      "Id": 3,
      "TruckActivityId": 0,
      "MediaId": "3fa85f64-5717-4562-b3fc-2c963f66afa8",
      "MediaUrl": "https://example.com/media/fuel-refill/fuel-pump.jpg",
      "AltText": "Ảnh đổ dầu - Ảnh cây dầu",
      "Caption": "Ảnh đổ dầu - Ảnh cây dầu",
      "MediaType": 12,
      "DisplayOrder": 3
    },
    {
      "Id": 4,
      "TruckActivityId": 0,
      "MediaId": "3fa85f64-5717-4562-b3fc-2c963f66afa9",
      "MediaUrl": "https://example.com/media/fuel-refill/receipt.jpg",
      "AltText": "Ảnh đổ dầu - Ảnh hóa đơn đổ dầu",
      "Caption": "Ảnh đổ dầu - Ảnh hóa đơn đổ dầu",
      "MediaType": 13,
      "DisplayOrder": 4
    }
  ]
}
```

**MediaType Values:**
- `10` - OdoBeforePhoto (Ảnh đồng hồ ODO trước khi đổ dầu)
- `11` - OdoAfterPhoto (Ảnh đồng hồ ODO sau khi đổ dầu)
- `12` - FuelPumpPhoto (Ảnh cây dầu - hiển thị số tiền, số lít, đơn giá)
- `13` - FuelReceiptPhoto (Ảnh hóa đơn đổ dầu)

> [!IMPORTANT]
> **MediaItems chỉ có trong GetRefillById**
> - API này trả về **đầy đủ thông tin** bao gồm danh sách hình ảnh (`MediaItems`)
> - Mỗi item trong `MediaItems` chứa URL để hiển thị ảnh
> - Hình ảnh được sắp xếp theo `DisplayOrder`

---

### 3.4. Lấy lịch sử đổ dầu theo xe

**Endpoint:** `GET /api/FuelCard/GetRefillsByTruck/{truckId}`

**Authorization:** `FuelCard.GetRefillsByTruck`

**Response:** Array of FuelRefillItem (giống SearchRefills)

---

### 3.5. Lấy lịch sử đổ dầu theo lái xe

**Endpoint:** `GET /api/FuelCard/GetRefillsByDriver/{driverId}`

**Authorization:** `FuelCard.GetRefillsByDriver`

**Response:** Array of FuelRefillItem (giống SearchRefills)

---

### 3.6. Xóa bản ghi đổ dầu

**Endpoint:** `POST /api/FuelCard/DeleteRefill/{id}`

**Authorization:** `FuelCard.DeleteRefill`

**Path Parameters:**
- `id` (int): ID của bản ghi đổ dầu cần xóa

**Response:**
```json
{
  "Result": 1,
  "Message": "Xóa bản ghi đổ dầu thành công"
}
```

**Error Response:**
```json
{
  "Result": 2,
  "Message": "Không tìm thấy bản ghi đổ dầu"
}
```

**Side Effects:**
- Soft delete bản ghi `FuelRefill` (IsDeleted = true)
- Soft delete tất cả `FuelRefillMedia` liên quan
- Cập nhật thông tin audit (UpdatedBy, UpdatedTime)

> [!WARNING]
> **Lưu ý quan trọng:**
> - API thực hiện **soft delete**, không xóa vật lý khỏi database
> - Tất cả media (ảnh) liên quan cũng được đánh dấu xóa
> - Không thể khôi phục sau khi xóa (cần implement API restore riêng nếu cần)

---

### 3.7. Xuất Excel lịch sử đổ dầu

**Endpoint:** `POST /api/FuelCard/ExportRefillsExcel`

**Authorization:** `FuelCard.ExportExcel`

**Request Body:**
```json
{
  "Search": {
    "PredicateObject": {
      "TruckId": 10,
      "Keyword": "29C"
    }
  },
  "Sort": {
    "Predicate": "RefillTime",
    "Reverse": true
  }
}
```

**Request Parameters:**
- `Search.PredicateObject.TruckId` (int, optional): Lọc theo ID xe
- `Search.PredicateObject.Keyword` (string, optional): Từ khóa tìm kiếm (biển số xe, tên lái xe, tên cây xăng)
- `Sort.Predicate` (string, optional): Trường sắp xếp (`RefillTime`, `DifferenceLiters`)
- `Sort.Reverse` (bool, optional): Sắp xếp giảm dần (true) hoặc tăng dần (false)

**Response:**
```json
{
  "Result": 1,
  "Message": "Xuất file Excel thành công",
  "FilePath": "E:\\Projects\\LADA\\SourceCode\\V23\\Ladaer.BackEnd\\Ladaer.BackEnd\\wwwroot\\exports\\fuel-refills\\LichSuDoDau_20260129_175701.xlsx",
  "FileName": "LichSuDoDau_20260129_175701.xlsx",
  "FileSize": 15234,
  "TotalRecords": 45,
  "DownloadUrl": "/api/FuelCardExcelExport/Download/LichSuDoDau_20260129_175701.xlsx",
  "ProcessStartTime": "2026-01-29T17:57:01+07:00",
  "ProcessEndTime": "2026-01-29T17:57:02+07:00",
  "ProcessingTimeMs": 1250
}
```

**Cột dữ liệu trong file Excel:**
1. STT
2. Biển số xe
3. Lái xe
4. Thẻ xăng
5. ODO trước
6. ODO hiện tại
7. Quãng đường (km)
8. Số tiền
9. Đơn giá
10. Số lít thực tế
11. Định mức (lít/100km)
12. Số lít khoán
13. Chênh lệch (lít)
14. Phương thức thanh toán
15. Cây xăng
16. Thời gian đổ dầu
17. Ghi chú

> [!NOTE]
> **Lưu ý quan trọng:**
> - API này xuất **toàn bộ dữ liệu** (không phân trang)
> - Sử dụng cùng logic filter và sort như API `SearchRefills`
> - File Excel được lưu trong thư mục `wwwroot/exports/fuel-refills/`
> - Tên file có format: `LichSuDoDau_yyyyMMdd_HHmmss.xlsx`
> - Header được format với background màu xanh, bold, và có border

---

## 4. Quản lý Số Dư Dầu Khoán

### 4.1. Lấy số dư dầu khoán của lái xe

**Endpoint:** `GET /api/FuelCard/GetDriverQuotaBalance/{driverId}`

**Authorization:** `FuelCard.GetDriverQuotaBalance`

**Response:**
```json
{
  "Id": 1,
  "DriverId": 50,
  "DriverName": "driver01",
  "DriverFullName": "Nguyễn Văn A",
  "TotalQuotaLiters": 165.5,
  "TotalActualLiters": 125.3,
  "TotalPaidLiters": 20.0,
  "BalanceLiters": 20.2,
  "LastUpdated": "2025-01-23T08:35:00+07:00"
}
```

**Formula:**
```
BalanceLiters = TotalQuotaLiters - TotalActualLiters - TotalPaidLiters
```

---

### 4.2. Tìm kiếm lịch sử chênh lệch dầu

**Endpoint:** `POST /api/FuelCard/SearchDriverQuotaHistory?driverId={driverId}`

**Authorization:** `FuelCard.SearchDriverQuotaHistory`

**Query Parameters:**
- `driverId` (int?, optional): Lọc theo lái xe

**Request Body:**
```json
{
  "Pagination": {
    "PageIndex": 1,
    "PageSize": 20
  },
  "Search": {
    "PredicateObject": {
      "Keyword": "driver01"
    }
  },
  "Sort": {
    "Predicate": "CreatedTime",
    "Reverse": true
  }
}
```

**Response:**
```json
{
  "Items": [
    {
      "Id": 1,
      "DriverId": 50,
      "DriverName": "driver01",
      "FuelRefillId": 100,
      "FuelQuotaPaymentId": null,
      "TruckId": 10,
      "TruckLicensePlate": "29C-12345",
      "TransactionType": 1,
      "TransactionTypeStr": "Đổ dầu",
      "DistanceKm": 300,
      "QuotaLiters": 33,
      "ActualLiters": 25,
      "DifferenceLiters": 8,
      "BalanceBeforeLiters": 12.2,
      "BalanceAfterLiters": 20.2,
      "Notes": "Đổ dầu: 25.00L, Khoán: 33.00L, Chênh lệch: 8.00L",
      "CreatedTime": "2025-01-23T08:35:00+07:00"
    }
  ],
  "TotalRecord": 1,
  "NumberOfPages": 1
}
```

---

### 4.3. Điều chỉnh số dư thủ công (Admin)

**Endpoint:** `POST /api/FuelCard/AdjustDriverQuota?driverId={driverId}&adjustmentLiters={adjustmentLiters}&notes={notes}`

**Authorization:** `FuelCard.AdjustDriverQuota`

**Query Parameters:**
- `driverId` (int): ID lái xe
- `adjustmentLiters` (decimal): Số lít điều chỉnh (+ hoặc -)
- `notes` (string): Lý do điều chỉnh

**Example:**
```
POST /api/FuelCard/AdjustDriverQuota?driverId=50&adjustmentLiters=5.5&notes=Bù trừ do sai sót tính toán
```

**Response:**
```json
{
  "Result": 1,
  "Message": "Điều chỉnh số dư thành công: 5.50 lít"
}
```

**Side Effects:**
- Cập nhật `DriverFuelQuotaBalance.BalanceLiters`
- Tạo `DriverFuelQuotaHistory` với TransactionType = Adjustment

---

## 5. Quản lý Thanh Toán Tiền Dầu

### 5.1. Tạo phiếu thanh toán

**Endpoint:** `POST /api/FuelCard/CreatePayment`

**Authorization:** `FuelCard.CreatePayment`

**Request Body:**
```json
{
  "Id": 0,
  "DriverId": 50,
  "PaidLiters": 20.2,
  "FuelUnitPrice": 21000,
  "PaymentDate": "2025-01-25T14:00:00+07:00",
  "PaymentMethod": "Chuyển khoản",
  "PaymentReference": "CK20250125-001",
  "Notes": "Thanh toán tháng 1/2025",
  "Status": 1
}
```

**Response:**
```json
{
  "Result": 1,
  "Message": "Tạo phiếu thanh toán thành công: 424,200 VND (20.20 lít)"
}
```

**Calculation:**
```
PaidAmount = PaidLiters × FuelUnitPrice
           = 20.2 × 21000
           = 424,200 VND
```

**Side Effects:**
- Tạo `FuelQuotaPayment`
- Cập nhật `DriverFuelQuotaBalance`:
  - `TotalPaidLiters += PaidLiters`
  - `BalanceLiters = TotalQuota - TotalActual - TotalPaid`
- Tạo `DriverFuelQuotaHistory` với TransactionType = Payment

---

### 5.2. Duyệt thanh toán

**Endpoint:** `POST /api/FuelCard/ApprovePayment/{id}`

**Authorization:** `FuelCard.ApprovePayment`

**Response:**
```json
{
  "Result": 1,
  "Message": "Duyệt thanh toán thành công"
}
```

**Side Effects:**
- Cập nhật Status: Pending → Approved
- Lưu thông tin người duyệt và thời gian duyệt

---

### 5.3. Tìm kiếm lịch sử thanh toán

**Endpoint:** `POST /api/FuelCard/SearchPayments`

**Authorization:** `FuelCard.SearchPayments`

**Request Body:**
```json
{
  "Pagination": {
    "PageIndex": 1,
    "PageSize": 15
  },
  "Search": {
    "PredicateObject": {
      "Keyword": "driver01"
    }
  },
  "Sort": {
    "Predicate": "PaymentDate",
    "Reverse": true
  }
}
```

**Response:**
```json
{
  "Items": [
    {
      "Id": 1,
      "DriverId": 50,
      "DriverName": "driver01",
      "DriverFullName": "Nguyễn Văn A",
      "PaymentDate": "2025-01-25T14:00:00+07:00",
      "PaidLiters": 20.2,
      "FuelUnitPrice": 21000,
      "PaidAmount": 424200,
      "BalanceBeforeLiters": 20.2,
      "BalanceAfterLiters": 0,
      "PaymentMethod": "Chuyển khoản",
      "PaymentReference": "CK20250125-001",
      "Notes": "Thanh toán tháng 1/2025",
      "ApprovedByUserId": 1,
      "ApprovedByUserName": "admin",
      "ApprovedDate": "2025-01-25T14:30:00+07:00",
      "Status": 2,
      "StatusStr": "Đã duyệt",
      "CreatedTime": "2025-01-25T14:00:00+07:00"
    }
  ],
  "TotalRecord": 1,
  "NumberOfPages": 1
}
```

**Sort Options:**
- `PaymentDate` - Theo ngày thanh toán
- `PaidAmount` - Theo số tiền
- `Status` - Theo trạng thái

---

## 6. Models & Enums

### 6.1. FuelCardStatus (Enum)

```csharp
1 = Active      // Đang hoạt động
2 = Inactive    // Ngừng hoạt động
3 = Lost        // Đã mất
4 = Expired     // Hết hạn
```

---

### 6.2. FuelCardTransactionStatus (Enum)

```csharp
1 = Success     // Thành công
2 = Failed      // Thất bại
3 = Pending     // Đang chờ
```

---

### 6.3. FuelQuotaTransactionType (Enum)

```csharp
1 = Refill      // Đổ dầu
2 = Payment     // Thanh toán cho lái xe
3 = Adjustment  // Điều chỉnh thủ công
4 = Reset       // Reset số dư
```

---

### 6.4. FuelQuotaPaymentStatus (Enum)

```csharp
1 = Pending     // Chờ duyệt
2 = Approved    // Đã duyệt
3 = Paid        // Đã thanh toán
4 = Cancelled   // Đã hủy
```

---

### 6.5. FuelRefillPaymentMethod (Enum)

```csharp
1 = Card        // Thẻ tín dụng
2 = Cash        // Tiền mặt
```

---

### 6.6. TruckActivityMediaType (Enum - Extended)

```csharp
1  = CheckInPhoto       // Ảnh khi nhận xe
2  = CheckOutPhoto      // Ảnh khi trả xe
3  = FuelRefillPhoto    // Ảnh đổ dầu (legacy)
10 = OdoBeforePhoto     // Ảnh đồng hồ ODO trước khi đổ dầu
11 = OdoAfterPhoto      // Ảnh đồng hồ ODO sau khi đổ dầu
12 = FuelPumpPhoto      // Ảnh cây dầu (hiển thị số tiền, số lít, đơn giá)
13 = FuelReceiptPhoto   // Ảnh hóa đơn đổ dầu
```

---

## 7. Workflow Tổng Quan

### 7.1. Quy trình đổ dầu cơ bản

```mermaid
sequenceDiagram
    participant Driver as Lái xe
    participant API as FuelCard API
    participant DB as Database
    
    Driver->>API: POST /RefillFuel<br/>(Upload ảnh + ODO)
    API->>DB: Lấy ODO trước đó
    API->>API: Tính toán:<br/>- Km đã chạy<br/>- Lít thực tế<br/>- Lít khoán<br/>- Chênh lệch
    API->>DB: Lưu FuelRefill
    API->>DB: Upload & lưu FuelRefillMedia
    API->>DB: Cập nhật Truck.LastRefillOdo
    API-->>Driver: Kết quả + Chênh lệch
```

> [!NOTE]
> **Thay đổi từ phiên bản trước:**
> - Không còn tạo `TruckActivity` cho mỗi lần đổ dầu
> - Media được lưu trực tiếp vào `FuelRefillMedia`

---

### 7.2. Quy trình thanh toán tiền dầu

```mermaid
sequenceDiagram
    participant Admin as Quản lý
    participant API as FuelCard API
    participant DB as Database
    
    Admin->>API: GET /GetDriverQuotaBalance/{driverId}
    API-->>Admin: BalanceLiters = 20.2 lít
    
    Admin->>API: POST /CreatePayment<br/>(PaidLiters=20.2, UnitPrice=21000)
    API->>DB: Kiểm tra số dư
    API->>DB: Tạo FuelQuotaPayment
    API->>DB: Cập nhật Balance (trừ đi)
    API->>DB: Tạo History
    API-->>Admin: Thành công: 424,200 VND
    
    Admin->>API: POST /ApprovePayment/{id}
    API->>DB: Cập nhật Status = Approved
    API-->>Admin: Duyệt thành công
```

---

### 7.3. Quy trình đối soát thẻ tín dụng

```mermaid
sequenceDiagram
    participant Admin as Quản lý
    participant API as FuelCard API
    participant DB as Database
    
    Admin->>API: POST /ImportStatement<br/>(Upload file Excel)
    Note right of API: TODO: Chưa implement
    
    Admin->>API: POST /SearchTransactions
    API-->>Admin: Danh sách giao dịch chưa đối soát
    
    Admin->>API: POST /ReconcileTransaction<br/>(TransactionId, FuelRefillId)
    API->>DB: Link Transaction ↔ FuelRefill
    API-->>Admin: Đối soát thành công
    
    Admin->>API: POST /MarkTransactionPaid/{id}
    API->>DB: IsPaidToBank = true
    API->>DB: Giảm CurrentDebt của thẻ
    API-->>Admin: Đánh dấu thành công
```

---

## 8. Error Responses

### 8.1. Validation Errors

```json
{
  "Result": 2,
  "Message": "Số lít thanh toán (25.00L) vượt quá số dư (20.20L)"
}
```

### 8.2. Not Found Errors

```json
{
  "Result": 2,
  "Message": "Không tìm thấy thẻ xăng dầu"
}
```

### 8.3. Business Logic Errors

```json
{
  "Result": 2,
  "Message": "ODO hiện tại (45000) phải lớn hơn ODO trước đó (45300)"
}
```

### 8.4. System Errors

```json
{
  "Result": 3,
  "Message": "Lỗi: Object reference not set to an instance of an object"
}
```

---

## 9. Business Rules

### 9.1. Đổ dầu

- ✅ Phải upload ít nhất 1 hình ảnh
- ✅ Số lượng Files phải bằng số lượng MediaTypes
- ✅ ODO hiện tại phải lớn hơn ODO trước
- ✅ Amount và UnitPrice phải > 0
- ✅ Tự động lấy ODO trước từ lần đổ trước hoặc lần nhận xe
- ✅ Tự động tính toán chênh lệch dầu khoán

### 9.2. Thanh toán

- ✅ PaidLiters không được vượt quá BalanceLiters
- ✅ BalanceLiters phải > 0
- ✅ Chỉ thanh toán khi Status = Pending
- ✅ Tự động tính PaidAmount = PaidLiters × FuelUnitPrice

### 9.3. Xóa thẻ

- ✅ Không xóa được thẻ đã có giao dịch
- ✅ Soft delete (IsDeleted = true)

---

## 10. Important Notes

### 10.1. Phân biệt Thẻ Tín Dụng vs Tài Khoản Ngân Hàng

**FuelCard (Thẻ Tín Dụng):**
- Là công nợ (Nợ phải trả)
- `CurrentDebt` = Tổng nợ chưa thanh toán cho ngân hàng
- Dùng cho báo cáo "Tổng nợ thẻ tín dụng"

**BankAccount (Tài Khoản Ngân Hàng):**
- Là tài sản
- `Balance` = Số dư tài khoản
- Dùng cho báo cáo "Tổng tài sản ngân hàng"

### 10.2. Data Integrity

**Copy-on-Reference Pattern:**
- `FuelRefill.FuelQuotaPer100Km` copy từ `Truck.FuelQuotaPer100Km` tại thời điểm đổ
- Đảm bảo dữ liệu lịch sử không thay đổi khi cập nhật định mức xe

### 10.3. Media Types Requirement

**Khi đổ dầu phải upload đủ 4 loại ảnh:**
1. **OdoBeforePhoto (10)** - Ảnh đồng hồ trước khi đổ
2. **OdoAfterPhoto (11)** - Ảnh đồng hồ sau khi đổ
3. **FuelPumpPhoto (12)** - Ảnh cây dầu (hiển thị số tiền, lít, đơn giá)
4. **FuelReceiptPhoto (13)** - Ảnh hóa đơn

**Cách upload:**
```javascript
// Example: FormData
const formData = new FormData();
formData.append('TruckId', 10);
formData.append('CurrentOdo', 45300);
formData.append('Amount', 500000);
formData.append('UnitPrice', 20000);
formData.append('PaymentMethod', 1);
formData.append('RefillTime', '2025-01-23T08:30:00+07:00');

// Upload 4 ảnh
formData.append('Files', odoBeforeImage);
formData.append('Files', odoAfterImage);
formData.append('Files', fuelPumpImage);
formData.append('Files', receiptImage);

// Khai báo loại ảnh tương ứng
formData.append('MediaTypes', 10); // OdoBeforePhoto
formData.append('MediaTypes', 11); // OdoAfterPhoto
formData.append('MediaTypes', 12); // FuelPumpPhoto
formData.append('MediaTypes', 13); // FuelReceiptPhoto
```

---

## 11. Testing Scenarios

### Scenario 1: Lái xe đổ dầu lần đầu tiên

**Given:**
- Lái xe mới nhận xe, CurrentOdo khi nhận xe = 45000 km
- Truck.FuelQuotaPer100Km = 11 lít/100km
- Chưa có lịch sử đổ dầu

**When:**
```json
POST /api/FuelCard/RefillFuel
{
  "TruckId": 10,
  "CurrentOdo": 45300,
  "Amount": 500000,
  "UnitPrice": 20000,
  "PaymentMethod": 1,
  "RefillTime": "2025-01-23T08:30:00+07:00"
}
```

**Expected:**
- PreviousOdo = 45000 (từ lần CheckIn)
- DistanceKm = 300
- ActualLiters = 25
- QuotaLiters = 33
- DifferenceLiters = +8 (lái xe được hưởng)
- DriverFuelQuotaBalance.BalanceLiters = 8

---

### Scenario 2: Thanh toán tiền dầu khoán

**Given:**
- DriverFuelQuotaBalance.BalanceLiters = 20.2 lít
- FuelUnitPrice hiện tại = 21,000 VND/lít

**When:**
```json
POST /api/FuelCard/CreatePayment
{
  "DriverId": 50,
  "PaidLiters": 20.2,
  "FuelUnitPrice": 21000,
  "PaymentDate": "2025-01-25T14:00:00+07:00",
  "PaymentMethod": "Chuyển khoản"
}
```

**Expected:**
- PaidAmount = 424,200 VND
- BalanceAfterLiters = 0
- TotalPaidLiters = 20.2
- FuelQuotaPayment được tạo với Status = Pending
- DriverFuelQuotaHistory được tạo với DifferenceLiters = -20.2

---

### Scenario 3: Lái xe đổ nhiều hơn định mức

**Given:**
- PreviousOdo = 45300
- Truck.FuelQuotaPer100Km = 11 lít/100km

**When:**
```json
POST /api/FuelCard/RefillFuel
{
  "CurrentOdo": 45500,
  "Amount": 600000,
  "UnitPrice": 20000
}
```

**Calculation:**
- DistanceKm = 200 km
- ActualLiters = 30 lít
- QuotaLiters = (200 × 11) / 100 = 22 lít
- DifferenceLiters = 22 - 30 = **-8 lít** (lái xe phải bù)

**Expected:**
- BalanceLiters giảm 8 lít
- Message: "Chênh lệch: -8.00 lít (phải bù)"

---

## 12. Database Tables

### FuelCards
- Quản lý thông tin thẻ tín dụng xăng dầu
- Link với Truck và Driver

### FuelCardTransactions
- Giao dịch thẻ từ sao kê ngân hàng
- Có thể đối soát với FuelRefill

### FuelCardStatementImports
- Lịch sử import file Excel sao kê

### FuelRefills
- Chi tiết mỗi lần đổ dầu
- Chứa đầy đủ thông tin ODO, số lít, tính toán khoán
- **Không còn** liên kết với `TruckActivity`

### FuelRefillMedias ⭐ **New**
- Media (ảnh) liên kết trực tiếp với FuelRefill
- Thay thế cho việc lưu media qua TruckActivityMedia
- Columns:
  - `FuelRefillId` - FK to FuelRefills
  - `MediaId` - FK to Media
  - `MediaType` - Loại ảnh (OdoBeforePhoto, OdoAfterPhoto, FuelPumpPhoto, FuelReceiptPhoto)
  - `DisplayOrder` - Thứ tự hiển thị

### DriverFuelQuotaBalances ⚠️ **Deprecated**
- Số dư dầu khoán hiện tại của mỗi lái xe
- Unique per driver
- **Note:** Chức năng quản lý dầu khoán đã bị loại bỏ

### DriverFuelQuotaHistories ⚠️ **Deprecated**
- Lịch sử biến động số dư (đổ dầu, thanh toán, điều chỉnh)
- **Note:** Chức năng quản lý dầu khoán đã bị loại bỏ

### FuelQuotaPayments ⚠️ **Deprecated**
- Phiếu thanh toán tiền dầu cho lái xe
- **Note:** Chức năng quản lý dầu khoán đã bị loại bỏ

---

## 13. Performance Considerations

### Indexes

Các indexes quan trọng đã được tạo:
- `IX_FuelCardTransactions_FuelCardId_TransactionDate` - Tìm kiếm giao dịch theo thẻ và ngày
- `IX_FuelRefills_TruckId_RefillTime` - Lịch sử đổ dầu theo xe
- `IX_FuelRefills_DriverId_RefillTime` - Lịch sử đổ dầu theo lái xe
- `IX_FuelRefillMedias_FuelRefillId` - Query media theo FuelRefill
- `IX_FuelRefillMedias_MediaId` - Query FuelRefill theo Media
- `IX_DriverFuelQuotaBalances_DriverId` (Unique) - Query số dư nhanh ⚠️ **Deprecated**
- `IX_DriverFuelQuotaHistories_DriverId_CreatedTime` - Lịch sử biến động ⚠️ **Deprecated**

---

## 14. Migration Guide

### Chạy Migration

```bash
# Tạo migration
dotnet ef migrations add AddFuelCardManagement -p Ladaer.BackEnd\Ladaer.BackEnd.csproj

# Áp dụng vào database
dotnet ef database update -p Ladaer.BackEnd\Ladaer.BackEnd.csproj

# Hoặc tạo SQL script
dotnet ef migrations script [FromVersion] AddFuelCardManagement -o migration.sql -p Ladaer.BackEnd\Ladaer.BackEnd.csproj
```

### Thêm Permissions vào Database

Cần thêm các permissions sau vào bảng Permissions:

```sql
-- FuelCard Management
INSERT INTO Permissions (Name, DisplayName, GroupName) VALUES
('FuelCard.Search', 'Tìm kiếm thẻ xăng dầu', 'FuelCard'),
('FuelCard.GetById', 'Xem chi tiết thẻ', 'FuelCard'),
('FuelCard.CreateOrUpdate', 'Tạo/Sửa thẻ', 'FuelCard'),
('FuelCard.Delete', 'Xóa thẻ', 'FuelCard'),
('FuelCard.AssignToTruck', 'Gán thẻ cho xe', 'FuelCard'),
('FuelCard.GetActiveCards', 'Xem danh sách thẻ hoạt động', 'FuelCard'),
('FuelCard.GetTotalDebt', 'Xem tổng nợ thẻ', 'FuelCard'),

-- FuelCardTransaction Management
('FuelCard.ImportStatement', 'Import sao kê thẻ', 'FuelCard'),
('FuelCard.SearchTransactions', 'Tìm kiếm giao dịch thẻ', 'FuelCard'),
('FuelCard.GetTransactionById', 'Xem chi tiết giao dịch', 'FuelCard'),
('FuelCard.ReconcileTransaction', 'Đối soát giao dịch', 'FuelCard'),
('FuelCard.MarkTransactionPaid', 'Đánh dấu đã thanh toán ngân hàng', 'FuelCard'),

-- FuelRefill Management
('FuelCard.RefillFuel', 'Đổ dầu', 'FuelCard'),
('FuelCard.SearchRefills', 'Tìm kiếm lịch sử đổ dầu', 'FuelCard'),
('FuelCard.GetRefillById', 'Xem chi tiết lần đổ dầu', 'FuelCard'),
('FuelCard.GetRefillsByTruck', 'Xem lịch sử đổ dầu theo xe', 'FuelCard'),
('FuelCard.GetRefillsByDriver', 'Xem lịch sử đổ dầu theo lái xe', 'FuelCard'),
('FuelCard.DeleteRefill', 'Xóa bản ghi đổ dầu', 'FuelCard'),

-- Driver Fuel Quota Management
('FuelCard.GetDriverQuotaBalance', 'Xem số dư dầu khoán', 'FuelCard'),
('FuelCard.SearchDriverQuotaHistory', 'Tìm kiếm lịch sử chênh lệch', 'FuelCard'),
('FuelCard.AdjustDriverQuota', 'Điều chỉnh số dư (Admin)', 'FuelCard'),

-- Fuel Quota Payment Management
('FuelCard.CreatePayment', 'Tạo phiếu thanh toán tiền dầu', 'FuelCard'),
('FuelCard.ApprovePayment', 'Duyệt thanh toán', 'FuelCard'),
('FuelCard.SearchPayments', 'Tìm kiếm lịch sử thanh toán', 'FuelCard');
```

### Gán Permissions cho Roles

```sql
-- Admin: Full access
INSERT INTO PermissionRoles (PermissionId, RoleId)
SELECT p.Id, r.Id 
FROM Permissions p
CROSS JOIN Roles r
WHERE p.Name LIKE 'FuelCard.%' AND r.Name = 'Admin';

-- Driver: Chỉ được đổ dầu và xem số dư của mình
INSERT INTO PermissionRoles (PermissionId, RoleId)
SELECT p.Id, r.Id 
FROM Permissions p
CROSS JOIN Roles r
WHERE p.Name IN ('FuelCard.RefillFuel', 'FuelCard.GetDriverQuotaBalance') 
  AND r.Name = 'Driver';

-- Accountant: Quản lý giao dịch và thanh toán
INSERT INTO PermissionRoles (PermissionId, RoleId)
SELECT p.Id, r.Id 
FROM Permissions p
CROSS JOIN Roles r
WHERE p.Name IN (
  'FuelCard.SearchTransactions',
  'FuelCard.ReconcileTransaction',
  'FuelCard.MarkTransactionPaid',
  'FuelCard.CreatePayment',
  'FuelCard.ApprovePayment',
  'FuelCard.SearchPayments'
) AND r.Name = 'Accountant';
```

---

## 15. TODO List

### High Priority
- [ ] Implement `FuelCardTransactionService.ImportStatementAsync()` - Parse Excel sao kê
- [ ] Viết Unit Tests cho các service
- [ ] Thêm validation rules phức tạp (duplicate detection)

### Medium Priority
- [ ] Tạo stored procedure cho báo cáo tổng hợp
- [ ] Export Excel lịch sử đổ dầu
- [ ] Dashboard widget hiển thị số dư dầu khoán

### Low Priority
- [ ] Auto-reconciliation (tự động đối soát dựa trên thời gian và số tiền)
- [ ] Notification khi số dư dầu khoán âm
- [ ] Báo cáo chi tiết tiêu hao nhiên liệu theo xe/lái xe

---

## 16. Contact & Support

Để biết thêm chi tiết về thiết kế hệ thống, vui lòng tham khảo:
- `docs/plans/FuelCard_Management_Plan.md` - Kế hoạch chi tiết và workflow
- `CLAUDE.md` - Hướng dẫn phát triển chung
