# API: Lấy Danh Sách Khách Hàng Cần Đối Soát

## Thông Tin Cơ Bản

**Endpoint:** `POST /api/CustomerReconciliationSlip/GetCustomersOverview`

**Mô tả:** API này lấy danh sách khách hàng có đơn hàng chưa được đối soát. Danh sách được phân trang và hỗ trợ tìm kiếm, lọc, sắp xếp.

**Yêu cầu xác thực:** Có (Bearer Token)

**Quyền cần thiết:** Một trong các quyền sau:
- `CustomerReconciliationSlip.View`
- `CustomerReconciliationSlip.Create`
- `Founder`

---

## Request

### Headers
```json
{
  "Authorization": "Bearer {access_token}",
  "Content-Type": "application/json"
}
```

### Body Parameters

Request sử dụng `SmartTableParam` để phân trang, tìm kiếm và sắp xếp:

```json
{
  "Pagination": {
    "PageIndex": 1,
    "PageSize": 20
  },
  "Search": {
    "PredicateObject": {
      "Keyword": "Nguyễn",
      "FromDate": "2024-11-01T00:00:00+07:00",
      "ToDate": "2024-12-05T23:59:59+07:00"
    }
  },
  "Sort": {
    "Predicate": "LatestOrderDate",
    "Reverse": true
  }
}
```

#### Chi tiết các tham số:

| Tham số | Kiểu | Bắt buộc | Mô tả |
|---------|------|----------|-------|
| `Pagination.PageIndex` | `int` | Có | Trang thứ mấy (bắt đầu từ 1) |
| `Pagination.PageSize` | `int` | Có | Số bản ghi trên mỗi trang (mặc định 200, tối đa 200) |
| `Search.PredicateObject.Keyword` | `string` | Không | Từ khóa tìm kiếm (tên, SĐT, email, username khách hàng) |
| `Search.PredicateObject.FromDate` | `DateTimeOffset` | Không | Ngày bắt đầu tìm đơn hàng (mặc định 30 ngày trước) |
| `Search.PredicateObject.ToDate` | `DateTimeOffset` | Không | Ngày kết thúc tìm đơn hàng (mặc định hôm nay) |
| `Sort.Predicate` | `string` | Không | Tên trường cần sắp xếp |
| `Sort.Reverse` | `boolean` | Không | `true` = Giảm dần, `false` = Tăng dần |

#### Các trường có thể sắp xếp:
- `CustomerName` - Tên khách hàng
- `CustomerCode` - Mã khách hàng
- `PendingOrderCount` - Số đơn chưa thanh toán
- `TotalPendingAmount` - Tổng tiền chưa thanh toán
- `OldestOrderDate` - Ngày đơn cũ nhất
- `LatestOrderDate` - Ngày đơn mới nhất
- `DaysFromOldest` - Số ngày từ đơn cũ nhất

---

## Response

### Success Response (200 OK)

```json
{
  "NumberOfRecords": 2,
  "NumberOfPages": 1,
  "Data": [
    {
      "Id": 1,
      "CustomerId": 125,
      "CustomerName": "Công ty TNHH ABC",
      "CustomerPhone": "0912345678",
      "CustomerCode": "KH001",
      "CustomerEmail": "contact@abc.com",
      "CustomerAddress": "123 Đường ABC, Quận 1, TP.HCM",
      "PendingOrderCount": 15,
      "TotalPendingAmount": 25500000,
      "TotalPendingAmountFormatted": "25,500,000 ₫",
      "OldestOrderDate": "2024-11-01T08:30:00+07:00",
      "LatestOrderDate": "2024-11-28T14:20:00+07:00",
      "DaysFromOldest": 34,
      "PriorityLevel": "Cao",
      "ManagerId": 5,
      "ManagerName": "Nguyễn Văn A",
      "BankAccountNumber": "1234567890",
      "BankAccountHolderName": "CONG TY TNHH ABC",
      "BankName": "Vietcombank",
      "CanCreateBill": true
    },
    {
      "Id": 2,
      "CustomerId": 256,
      "CustomerName": "Công ty CP XYZ",
      "CustomerPhone": "0987654321",
      "CustomerCode": "KH002",
      "CustomerEmail": "info@xyz.vn",
      "CustomerAddress": "456 Đường XYZ, Quận 3, TP.HCM",
      "PendingOrderCount": 8,
      "TotalPendingAmount": 12300000,
      "TotalPendingAmountFormatted": "12,300,000 ₫",
      "OldestOrderDate": "2024-11-15T10:00:00+07:00",
      "LatestOrderDate": "2024-11-27T16:45:00+07:00",
      "DaysFromOldest": 20,
      "PriorityLevel": "Trung bình",
      "ManagerId": 7,
      "ManagerName": "Trần Thị B",
      "BankAccountNumber": "9876543210",
      "BankAccountHolderName": "CONG TY CP XYZ",
      "BankName": "Techcombank",
      "CanCreateBill": true
    }
  ]
}
```

### Response Fields

#### Root Level

| Trường | Kiểu | Mô tả |
|--------|------|-------|
| `NumberOfRecords` | `int` | Tổng số bản ghi |
| `NumberOfPages` | `int` | Tổng số trang |
| `Data` | `array` | Danh sách khách hàng |

#### Data Item (CustomerTransferPaymentSummaryItem)

| Trường | Kiểu | Mô tả |
|--------|------|-------|
| `Id` | `int` | ID của item (thường bằng CustomerId) |
| `CustomerId` | `int` | ID khách hàng |
| `CustomerName` | `string` | Tên khách hàng |
| `CustomerPhone` | `string` | Số điện thoại khách hàng |
| `CustomerCode` | `string` | Mã khách hàng |
| `CustomerEmail` | `string` | Email khách hàng |
| `CustomerAddress` | `string` | Địa chỉ khách hàng |
| `PendingOrderCount` | `int` | Số đơn hàng chưa đối soát |
| `TotalPendingAmount` | `decimal` | Tổng tiền chưa thanh toán (VNĐ) |
| `TotalPendingAmountFormatted` | `string` | Tổng tiền đã format |
| `OldestOrderDate` | `string` (ISO 8601) | Ngày đơn hàng cũ nhất chưa thanh toán |
| `LatestOrderDate` | `string` (ISO 8601) | Ngày đơn hàng mới nhất chưa thanh toán |
| `DaysFromOldest` | `int` | Số ngày từ đơn hàng cũ nhất đến hiện tại |
| `PriorityLevel` | `string` | Mức độ ưu tiên: "Cao" (≥30 ngày), "Trung bình" (≥15 ngày), "Thấp" (<15 ngày) |
| `ManagerId` | `int` | ID nhân viên quản lý khách hàng |
| `ManagerName` | `string` | Tên nhân viên quản lý |
| `BankAccountNumber` | `string` | Số tài khoản ngân hàng của khách hàng |
| `BankAccountHolderName` | `string` | Tên chủ tài khoản |
| `BankName` | `string` | Tên ngân hàng |
| `CanCreateBill` | `boolean` | Có thể tạo phiếu đối soát hay không |

---

## Error Response

### 401 Unauthorized
```json
{
  "type": "https://tools.ietf.org/html/rfc7235#section-3.1",
  "title": "Unauthorized",
  "status": 401,
  "traceId": "00-xxxxx"
}
```

**Nguyên nhân:** Token không hợp lệ hoặc đã hết hạn

---

### 403 Forbidden
```json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.3",
  "title": "Forbidden",
  "status": 403,
  "traceId": "00-xxxxx"
}
```

**Nguyên nhân:** User không có quyền truy cập endpoint này

---

### 400 Bad Request
```json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Pagination.Number": [
      "The field Number must be between 1 and 100."
    ]
  },
  "traceId": "00-xxxxx"
}
```

**Nguyên nhân:** Tham số không hợp lệ

---

### 500 Internal Server Error
```json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.6.1",
  "title": "An error occurred while processing your request.",
  "status": 500,
  "traceId": "00-xxxxx"
}
```

**Nguyên nhân:** Lỗi server

---

## Business Logic

### Quy tắc lọc dữ liệu (sử dụng Stored Procedure `sp_SearchCustomersForReconciliation`):

1. **Chỉ lấy khách hàng có đơn hàng chưa đối soát:**
   - Đơn COD đã về kho đầu (`CodStage = 9`) và `CollectAmount > 0`
   - Đơn chuyển khoản chưa thu cước (`PayFeeType = 3` và `TransferFeeStage = 1`)

2. **Loại trừ đơn đã trong phiếu đối soát/thanh toán:**
   - Đã có trong `CustomerReconciliationSlip` (trừ status Cancelled)
   - Đã có trong `Reconciliation` (trừ status Cancelled/Deleted)
   - Đã có trong `TransferPaymentBill` (trừ status Cancelled/Deleted)

3. **Phân quyền theo Role:**
   - Sale: Chỉ thấy khách hàng do mình quản lý (`ManagerId` filter)
   - Kế toán/Admin/Founder: Thấy tất cả khách hàng

4. **Khoảng thời gian:**
   - Mặc định lấy đơn trong 30 ngày gần nhất nếu không truyền `FromDate`/`ToDate`

### Tính toán số liệu:

- **TotalCodAmount**: Tổng `CollectAmount` của các đơn COD chưa đối soát
- **TotalCodFee**: Tổng cước đơn COD = `Fee + PickupSubFee + DeliverySubFee + CodFee + InsuranceFee + ReturnFee - Discount`
- **TotalTransferFee**: Tổng `TransferFee` của đơn chuyển khoản chưa thu
- **EstimatedNetAmount**: Số tiền ước tính trả khách = `TotalCodAmount - TotalCodFee - TotalTransferFee`

### Tính toán PriorityLevel:
```csharp
DaysFromOldest >= 30  => "Cao"
DaysFromOldest >= 15  => "Trung bình"
DaysFromOldest < 15   => "Thấp"
```

### Tìm kiếm:
Khi có `Search.PredicateObject.Keyword`, hệ thống tìm kiếm trong các trường:
- Tên khách hàng (`FullName`)
- Số điện thoại (`PhoneNumber`)
- Email (`Email`)
- Username (`UserName`)

---

## Ví dụ sử dụng

### Example 1: Lấy trang đầu tiên (20 bản ghi)

**Request:**
```bash
curl -X POST "https://api.yourdomain.com/api/CustomerReconciliationSlip/GetCustomersOverview" \
  -H "Authorization: Bearer {your_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "Pagination": {
      "PageIndex": 1,
      "PageSize": 20
    },
    "Sort": {
      "Predicate": "DaysFromOldest",
      "Reverse": true
    }
  }'
```

---

### Example 2: Tìm kiếm theo tên khách hàng

**Request:**
```bash
curl -X POST "https://api.yourdomain.com/api/CustomerReconciliationSlip/GetCustomersOverview" \
  -H "Authorization: Bearer {your_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "Pagination": {
      "PageIndex": 1,
      "PageSize": 20
    },
    "Search": {
      "PredicateObject": "ABC"
    }
  }'
```

---

### Example 3: Lọc theo khoảng thời gian

**Request:**
```bash
curl -X POST "https://api.yourdomain.com/api/CustomerReconciliationSlip/GetCustomersOverview" \
  -H "Authorization: Bearer {your_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "Pagination": {
      "PageIndex": 1,
      "PageSize": 20
    },
    "Search": {
      "PredicateObject": {
        "FromDate": "2024-11-01T00:00:00+07:00",
        "ToDate": "2024-11-30T23:59:59+07:00"
      }
    },
    "Sort": {
      "Predicate": "TotalPendingAmount",
      "Reverse": true
    }
  }'
```

---

### Example 4: Sắp xếp theo tổng tiền giảm dần

**Request:**
```bash
curl -X POST "https://api.yourdomain.com/api/CustomerReconciliationSlip/GetCustomersOverview" \
  -H "Authorization: Bearer {your_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "Pagination": {
      "PageIndex": 1,
      "PageSize": 20
    },
    "Sort": {
      "Predicate": "TotalPendingAmount",
      "Reverse": true
    }
  }'
```

---

## Notes

- **Định dạng thời gian:** Sử dụng ISO 8601 với timezone (`2024-11-01T08:30:00+07:00`)
- **Định dạng tiền tệ:** Format theo chuẩn Việt Nam với ký hiệu `₫`
- **Pagination:** 
  - `PageIndex` bắt đầu từ 1 (trang thứ nhất là 1, trang thứ hai là 2, ...)
  - `PageSize` mặc định 200, tối đa 200 bản ghi/trang
- **Security:** API yêu cầu Bearer Token hợp lệ và role phù hợp
- **Performance:** 
  - Sử dụng **Stored Procedure** `sp_SearchCustomersForReconciliation` để tối ưu hiệu năng
  - Query được SQL Server optimize và compile trước
  - Phân trang được xử lý ở database level với `OFFSET...FETCH NEXT`
  - Suitable cho xử lý khối lượng dữ liệu lớn

---

## Change Log

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | 2024-12-05 | Development Team | Initial release |
| 1.1 | 2024-12-05 | Development Team | Refactored to use stored procedure; Added FromDate/ToDate filter parameters |

---

## Related APIs

- `GET /api/CustomerReconciliationSlip/GetReconciliableOrders` - Lấy danh sách đơn hàng có thể đối soát
- `POST /api/CustomerReconciliationSlip/Create` - Tạo phiếu đối soát
- `POST /api/CustomerReconciliationSlip/Search` - Tìm kiếm phiếu đối soát đã tạo
