# API: Tìm Kiếm Phiếu Đối Soát Khách Hàng

## Thông Tin Cơ Bản

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

**Mô tả:** API này tìm kiếm và lấy danh sách các phiếu đối soát khách hàng đã được tạo trong hệ thống. Hỗ trợ phân trang, tìm kiếm theo từ khóa, lọc theo trạng thái và 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`
- `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": "CR-241208",
      "Status": 2
    }
  },
  "Sort": {
    "Predicate": "CreatedTime",
    "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 20, tối đa 200) |
| `Search.PredicateObject.Keyword` | `string` | Không | Từ khóa tìm kiếm (mã phiếu hoặc tên khách hàng) |
| `Search.PredicateObject.Status` | `int` | Không | Trạng thái phiếu (xem bảng Status Values bên dưới) |
| `Sort.Predicate` | `string` | Không | Tên trường cần sắp xếp (mặc định: `CreatedTime`) |
| `Sort.Reverse` | `boolean` | Không | `true` = Giảm dần, `false` = Tăng dần (mặc định: `true`) |

#### Status Values (CustomerReconciliationSlipStatus):

| Giá trị | Tên | Mô tả |
|---------|-----|-------|
| `1` | Draft | Nháp (chưa sử dụng trong flow hiện tại) |
| `2` | PendingApproval | Chờ duyệt - Phiếu mới tạo, chờ kế toán duyệt |
| `3` | Approved | Đã duyệt - Kế toán đã duyệt, chờ thanh toán |
| `4` | Completed | Hoàn thành - Đã thanh toán xong |
| `8` | Rejected | Từ chối - Kế toán từ chối phiếu |

#### Các trường có thể sắp xếp:

- `Code` - Mã phiếu
- `CustomerName` - Tên khách hàng
- `Type` - Loại phiếu (Thu/Chi)
- `Status` - Trạng thái
- `TotalCodAmount` - Tổng tiền COD
- `TotalFeeAmount` - Tổng phí vận chuyển
- `FinalAmount` - Số tiền cuối cùng
- `OrderCount` - Số lượng đơn hàng
- `CreatedTime` - Thời gian tạo (mặc định)
- `ApprovedTime` - Thời gian duyệt
- `CompletedTime` - Thời gian hoàn thành

---

## Response

### Success Response (200 OK)

```json
{
  "NumberOfRecords": 2,
  "NumberOfPages": 1,
  "Data": [
    {
      "Id": 1234,
      "Code": "CR-241208-A3F2",
      "CustomerId": 125,
      "CustomerName": "Công ty TNHH ABC",
      "CustomerPhone": "0912345678",
      "Type": 1,
      "TypeName": "Phiếu Thu",
      "Status": 2,
      "StatusName": "Chờ duyệt",
      "TotalCodAmount": 45000000,
      "TotalFeeAmount": 18000000,
      "FinalAmount": 27000000,
      "FinalAmountFormatted": "27,000,000 ₫",
      "OrderCount": 15,
      "CreatedByUserName": "nguyenvana",
      "CreatedTime": "2024-12-08T10:30:00+07:00",
      "ApprovedByUsername": "",
      "ApprovedTime": null,
      "CompletedByUsername": "",
      "CompletedTime": null,
      "InternalNote": "Đối soát tháng 11/2024 - Đợt 1",
      "PaymentMethod": "",
      "TransactionCode": "",
      "ProofImageUrl": ""
    },
    {
      "Id": 1235,
      "Code": "CR-241207-B5D1",
      "CustomerId": 256,
      "CustomerName": "Công ty CP XYZ",
      "CustomerPhone": "0987654321",
      "Type": 1,
      "TypeName": "Phiếu Thu",
      "Status": 4,
      "StatusName": "Hoàn thành",
      "TotalCodAmount": 20000000,
      "TotalFeeAmount": 7700000,
      "FinalAmount": 12300000,
      "FinalAmountFormatted": "12,300,000 ₫",
      "OrderCount": 8,
      "CreatedByUserName": "tranthib",
      "CreatedTime": "2024-12-07T14:20:00+07:00",
      "ApprovedByUsername": "ketoan01",
      "ApprovedTime": "2024-12-07T15:00:00+07:00",
      "CompletedByUsername": "ketoan01",
      "CompletedTime": "2024-12-08T09:30:00+07:00",
      "InternalNote": "Đối soát tháng 11/2024",
      "PaymentMethod": "Vietcombank",
      "TransactionCode": "FT24120712345",
      "ProofImageUrl": "https://storage.example.com/proofs/1235.jpg"
    }
  ]
}
```

### 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 phiếu đối soát |

#### Data Item (CustomerReconciliationSlipItem)

| Trường | Kiểu | Mô tả |
|--------|------|-------|
| `Id` | `int` | ID phiếu đối soát |
| `Code` | `string` | Mã phiếu (VD: CR-241208-A3F2) |
| `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 |
| `Type` | `int` | Loại phiếu: 1 = Receivable (Thu), 2 = Payable (Chi) |
| `TypeName` | `string` | Tên loại phiếu: "Phiếu Thu" hoặc "Phiếu Chi" |
| `Status` | `int` | Trạng thái phiếu (xem bảng Status Values) |
| `StatusName` | `string` | Tên trạng thái (VD: "Chờ duyệt", "Đã duyệt", "Hoàn thành") |
| `TotalCodAmount` | `decimal` | Tổng tiền COD thu hộ (VNĐ) |
| `TotalFeeAmount` | `decimal` | Tổng phí vận chuyển (VNĐ) |
| `FinalAmount` | `decimal` | Số tiền cuối cùng = \|TotalCodAmount - TotalFeeAmount\| (VNĐ) |
| `FinalAmountFormatted` | `string` | Số tiền cuối cùng đã format (VD: "27,000,000 ₫") |
| `OrderCount` | `int` | Số lượng đơn hàng trong phiếu |
| `CreatedByUserName` | `string` | Username người tạo phiếu |
| `CreatedTime` | `string` (ISO 8601) | Thời gian tạo phiếu |
| `ApprovedByUsername` | `string` | Username người duyệt phiếu (rỗng nếu chưa duyệt) |
| `ApprovedTime` | `string` (ISO 8601) / `null` | Thời gian duyệt phiếu |
| `CompletedByUsername` | `string` | Username người hoàn thành phiếu (rỗng nếu chưa hoàn thành) |
| `CompletedTime` | `string` (ISO 8601) / `null` | Thời gian hoàn thành phiếu |
| `InternalNote` | `string` | Ghi chú nội bộ |
| `PaymentMethod` | `string` | Phương thức thanh toán (tên ngân hàng/ví) |
| `TransactionCode` | `string` | Mã giao dịch ngân hàng |
| `ProofImageUrl` | `string` | URL ảnh chứng từ thanh toán |

---

## 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 `CustomerReconciliationSlip.View` hoặc `Founder`

---

### 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.PageIndex": [
      "The field PageIndex must be between 1 and 2147483647."
    ]
  },
  "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

### 1. Phân Quyền Xem Dữ Liệu

Hệ thống áp dụng phân quyền theo role:

| Role | Phạm vi truy cập |
|------|------------------|
| **Founder** | Xem tất cả phiếu đối soát trong hệ thống |
| **CustomerReconciliationSlip.View** (Kế toán) | Xem tất cả phiếu đối soát trong hệ thống |
| **Sale/Manager** (không có role đặc biệt) | Chỉ xem phiếu do mình tạo HOẶC phiếu của khách hàng mình quản lý |

**Logic phân quyền:**
```csharp
if (!isFounder && !isAccountant)
{
    // Sale chỉ thấy phiếu do mình tạo hoặc khách hàng mình quản lý
    query = query.Where(x => x.CreatedBy == userId || x.Customer.ManagerId == userId);
}
```

### 2. Tìm Kiếm (Search)

Khi có `Search.PredicateObject.Keyword`, hệ thống tìm kiếm trong các trường:
- **Mã phiếu** (`Code`): Tìm kiếm chứa từ khóa (VD: "CR-241208")
- **Tên khách hàng** (`Customer.FullName`): Tìm kiếm chứa từ khóa (VD: "ABC")

**Lọc theo trạng thái:**
- Nếu có `Search.PredicateObject.Status`, chỉ lấy phiếu có trạng thái tương ứng
- Giá trị Status: 1 (Draft), 2 (PendingApproval), 3 (Approved), 4 (Completed), 8 (Rejected)

### 3. Sắp Xếp (Sort)

- **Mặc định:** Sắp xếp theo `CreatedTime` giảm dần (phiếu mới nhất lên đầu)
- User có thể chọn sắp xếp theo bất kỳ trường nào trong danh sách "Các trường có thể sắp xếp"
- `Reverse = true`: Giảm dần (Z-A, 9-0, mới nhất → cũ nhất)
- `Reverse = false`: Tăng dần (A-Z, 0-9, cũ nhất → mới nhất)

### 4. Phân Trang (Pagination)

- `PageIndex` bắt đầu từ 1
- `PageSize` tối đa 200 bản ghi/trang
- Response trả về `NumberOfRecords` (tổng số bản ghi) và `NumberOfPages` (tổng số trang)

---

## Ví dụ sử dụng

### Example 1: Lấy tất cả phiếu (trang đầu tiên)

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

**Response:** Danh sách 20 phiếu mới nhất (sắp xếp theo `CreatedTime` giảm dần)

---

### Example 2: Tìm kiếm theo mã phiếu

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

**Response:** Danh sách phiếu có mã chứa "CR-241208"

---

### Example 3: Lọc phiếu đang chờ duyệt

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

**Response:** Danh sách phiếu có trạng thái "Chờ duyệt" (PendingApproval)

---

### Example 4: Tìm kiếm theo tên khách hàng và lọc theo trạng thái

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

**Response:** Danh sách phiếu của khách hàng có tên chứa "ABC" và đã hoàn thành

---

### Example 5: Sắp xếp theo số tiền cuối cùng giảm dần

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

**Response:** Danh sách phiếu sắp xếp theo số tiền cuối cùng từ cao đến thấp

---

### Example 6: Frontend integration (JavaScript)

```javascript
// Service function để tìm kiếm phiếu đối soát
async function searchReconciliationSlips(params) {
  try {
    const response = await fetch(
      '/api/CustomerReconciliationSlip/Search',
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${getAccessToken()}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(params)
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const result = await response.json();
    return result;
  } catch (error) {
    console.error('Failed to search reconciliation slips:', error);
    throw error;
  }
}

// Sử dụng - Lấy phiếu chờ duyệt
const pendingSlips = await searchReconciliationSlips({
  Pagination: {
    PageIndex: 1,
    PageSize: 20
  },
  Search: {
    PredicateObject: {
      Status: 2 // PendingApproval
    }
  },
  Sort: {
    Predicate: 'CreatedTime',
    Reverse: true
  }
});

console.log(`Tìm thấy ${pendingSlips.NumberOfRecords} phiếu chờ duyệt`);
console.log(`Tổng số trang: ${pendingSlips.NumberOfPages}`);
```

---

### Example 7: Vue.js Component với Filters

```javascript
// Component Vue.js
export default {
  data() {
    return {
      slips: [],
      totalRecords: 0,
      totalPages: 0,
      currentPage: 1,
      pageSize: 20,
      keyword: '',
      selectedStatus: null,
      loading: false,
      statusOptions: [
        { value: null, label: 'Tất cả trạng thái' },
        { value: 2, label: 'Chờ duyệt' },
        { value: 3, label: 'Đã duyệt' },
        { value: 4, label: 'Hoàn thành' },
        { value: 8, label: 'Từ chối' }
      ]
    };
  },
  
  methods: {
    async loadSlips() {
      this.loading = true;
      
      try {
        const params = {
          Pagination: {
            PageIndex: this.currentPage,
            PageSize: this.pageSize
          },
          Search: {
            PredicateObject: {}
          },
          Sort: {
            Predicate: 'CreatedTime',
            Reverse: true
          }
        };
        
        // Add filters
        if (this.keyword) {
          params.Search.PredicateObject.Keyword = this.keyword;
        }
        
        if (this.selectedStatus !== null) {
          params.Search.PredicateObject.Status = this.selectedStatus;
        }
        
        const result = await searchReconciliationSlips(params);
        
        this.slips = result.Data;
        this.totalRecords = result.NumberOfRecords;
        this.totalPages = result.NumberOfPages;
      } catch (error) {
        this.$message.error('Không thể tải danh sách phiếu đối soát');
      } finally {
        this.loading = false;
      }
    },
    
    handleSearch() {
      this.currentPage = 1;
      this.loadSlips();
    },
    
    handlePageChange(page) {
      this.currentPage = page;
      this.loadSlips();
    }
  },
  
  mounted() {
    this.loadSlips();
  }
};
```

---

## TypeScript Interface

```typescript
// Request Model
interface SearchReconciliationSlipsRequest {
  Pagination: {
    PageIndex: number;
    PageSize: number;
  };
  Search?: {
    PredicateObject?: {
      Keyword?: string;
      Status?: CustomerReconciliationSlipStatus;
    };
  };
  Sort?: {
    Predicate?: string;
    Reverse?: boolean;
  };
}

// Response Model
interface CustomerReconciliationSlipItem {
  Id: number;
  Code: string;
  CustomerId: number;
  CustomerName: string;
  CustomerPhone: string;
  Type: CustomerReconciliationSlipType;
  TypeName: string;
  Status: CustomerReconciliationSlipStatus;
  StatusName: string;
  TotalCodAmount: number;
  TotalFeeAmount: number;
  FinalAmount: number;
  FinalAmountFormatted: string;
  OrderCount: number;
  CreatedByUserName: string;
  CreatedTime: string;
  ApprovedByUsername: string;
  ApprovedTime: string | null;
  CompletedByUsername: string;
  CompletedTime: string | null;
  InternalNote: string;
  PaymentMethod: string;
  TransactionCode: string;
  ProofImageUrl: string;
}

interface SearchReconciliationSlipsResponse {
  NumberOfRecords: number;
  NumberOfPages: number;
  Data: CustomerReconciliationSlipItem[];
}

// Enums
enum CustomerReconciliationSlipType {
  Receivable = 1,
  Payable = 2
}

enum CustomerReconciliationSlipStatus {
  Draft = 1,
  PendingApproval = 2,
  Approved = 3,
  Completed = 4,
  Rejected = 8
}

// API Function
async function searchReconciliationSlips(
  request: SearchReconciliationSlipsRequest
): Promise<SearchReconciliationSlipsResponse> {
  const response = await fetch('/api/CustomerReconciliationSlip/Search', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${getAccessToken()}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(request)
  });

  if (!response.ok) {
    throw new Error('Failed to search reconciliation slips');
  }

  return response.json();
}
```

---

## Workflow

API này thường được sử dụng trong các màn hình sau:

### 1. Màn hình danh sách phiếu đối soát (List View)
- Hiển thị tất cả phiếu đối soát
- Cho phép tìm kiếm, lọc, sắp xếp
- Phân trang kết quả

### 2. Dashboard kế toán
- Lọc phiếu chờ duyệt (`Status = 2`)
- Hiển thị số lượng phiếu cần xử lý

### 3. Báo cáo đối soát
- Lọc theo khoảng thời gian
- Thống kê theo trạng thái
- Xuất báo cáo

---

## Notes

- **Định dạng thời gian:** Sử dụng ISO 8601 với timezone (`2024-12-08T10:30:00+07:00`)
- **Định dạng tiền tệ:** `FinalAmountFormatted` đã được format theo chuẩn Việt Nam với ký hiệu `₫`
- **Pagination:** 
  - `PageIndex` bắt đầu từ 1
  - `PageSize` mặc định 20, tối đa 200 bản ghi/trang
- **Default Sort:** Nếu không truyền `Sort`, mặc định sắp xếp theo `CreatedTime` giảm dần
- **Security:** 
  - API yêu cầu Bearer Token hợp lệ
  - Phân quyền theo role: Founder/Kế toán xem tất cả, Sale chỉ xem phiếu liên quan
- **Performance:** 
  - Sử dụng `Include()` để eager load Customer
  - Pagination được xử lý ở database level
  - Index trên các trường thường xuyên search/sort (Code, CreatedTime, Status)

---

## Status Flow

Luồng trạng thái của phiếu đối soát:

```
Draft (1) → [Không sử dụng trong flow hiện tại]
    ↓
PendingApproval (2) → Approved (3) → Completed (4)
    ↓                      ↓
    → Rejected (8) ←-------┘
```

**Chi tiết:**
1. **PendingApproval**: Phiếu mới tạo, chờ kế toán duyệt
2. **Approved**: Kế toán đã duyệt, chờ thanh toán
3. **Completed**: Đã thanh toán xong
4. **Rejected**: Kế toán từ chối (có thể từ chối ở bất kỳ trạng thái nào trước Completed)

---

## Filter Combinations

### Các kết hợp filter phổ biến:

| Use Case | Keyword | Status | Sort |
|----------|---------|--------|------|
| Phiếu chờ duyệt | - | 2 | CreatedTime DESC |
| Phiếu đã duyệt chờ thanh toán | - | 3 | ApprovedTime DESC |
| Phiếu hoàn thành | - | 4 | CompletedTime DESC |
| Tìm phiếu theo mã | "CR-241208" | - | CreatedTime DESC |
| Phiếu của khách hàng | "ABC" | - | CreatedTime DESC |
| Phiếu giá trị cao | - | - | FinalAmount DESC |

---

## Change Log

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | 2024-12-08 | Development Team | Initial release |

---

## Related APIs

- `POST /api/CustomerReconciliationSlip/GetCustomersOverview` - Lấy danh sách khách hàng cần đối soát
- `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 mới
- `GET /api/CustomerReconciliationSlip/GetById/{id}` - Lấy chi tiết phiếu đối soát
- `PUT /api/CustomerReconciliationSlip/Approve/{id}` - Duyệt phiếu đối soát
- `POST /api/CustomerReconciliationSlip/Reject` - Từ chối phiếu đối soát
- `POST /api/CustomerReconciliationSlip/Complete` - Hoàn thành phiếu (xác nhận thanh toán)
- `GET /api/CustomerReconciliationSlip/GetLogs/{id}` - Lấy lịch sử thao tác
