# API: Lấy Danh Sách Đơn Hàng Có Thể Đối Soát

## Thông Tin Cơ Bản

**Endpoint:** `GET /api/CustomerReconciliationSlip/GetReconciliableOrders`

**Mô tả:** API này lấy danh sách các đơn hàng của một khách hàng cụ thể có thể đưa vào phiếu đối soát. Chỉ bao gồm những đơn hàng đã giao thành công hoặc đã hoàn và chưa nằm trong bất kỳ phiếu đối soát nào đang xử lý.

**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"
}
```

### Query Parameters

| Tham số | Kiểu | Bắt buộc | Mô tả |
|---------|------|----------|-------|
| `customerId` | `int` | Có | ID của khách hàng cần lấy danh sách đơn hàng |

### Ví dụ URL
```
GET /api/CustomerReconciliationSlip/GetReconciliableOrders?customerId=125
```

---

## Response

### Success Response (200 OK)

```json
[
  {
    "Id": 0,
    "CustomerReconciliationSlipId": 0,
    "OrderId": 10523,
    "OrderCode": "LD2411280001",
    "PartnerCode": "GHN123456",
    "AppliedCodAmount": 500000,
    "AppliedFeeAmount": 25000,
    "NetAmount": 475000,
    "DeliveredDate": "2024-11-28T14:30:00+07:00",
    "CodCollectedDate": "2024-11-29T10:00:00+07:00",
    "ChargeWeight": 1.5,
    "ShippingFee": 25000,
    "DeliveryAddress": "123 Đường ABC, Phường 1, Quận 1, TP.HCM",
    "ReceiverName": "Nguyễn Văn A",
    "ReceiverPhone": "0901234567",
    "Note": "Giao hàng buổi sáng"
  },
  {
    "Id": 0,
    "CustomerReconciliationSlipId": 0,
    "OrderId": 10524,
    "OrderCode": "LD2411280002",
    "PartnerCode": "GHN123457",
    "AppliedCodAmount": 1200000,
    "AppliedFeeAmount": 35000,
    "NetAmount": 1165000,
    "DeliveredDate": "2024-11-28T16:45:00+07:00",
    "CodCollectedDate": "2024-11-29T11:00:00+07:00",
    "ChargeWeight": 2.3,
    "ShippingFee": 35000,
    "DeliveryAddress": "456 Đường XYZ, Phường 5, Quận 3, TP.HCM",
    "ReceiverName": "Trần Thị B",
    "ReceiverPhone": "0987654321",
    "Note": ""
  },
  {
    "Id": 0,
    "CustomerReconciliationSlipId": 0,
    "OrderId": 10530,
    "OrderCode": "LD2411290005",
    "PartnerCode": "GHTK789012",
    "AppliedCodAmount": 0,
    "AppliedFeeAmount": 18000,
    "NetAmount": -18000,
    "DeliveredDate": null,
    "CodCollectedDate": null,
    "ChargeWeight": 0.8,
    "ShippingFee": 18000,
    "DeliveryAddress": "789 Đường DEF, Phường 10, Quận 7, TP.HCM",
    "ReceiverName": "Lê Văn C",
    "ReceiverPhone": "0912345678",
    "Note": "Đơn hoàn - khách không nhận"
  }
]
```

### Response Fields

#### CustomerReconciliationSlipDetailItem

| Trường | Kiểu | Mô tả |
|--------|------|-------|
| `Id` | `int` | ID của item (luôn = 0 vì đơn hàng chưa thuộc phiếu nào) |
| `CustomerReconciliationSlipId` | `int` | ID phiếu đối soát (= 0 vì chưa được gán vào phiếu) |
| `OrderId` | `int` | ID đơn hàng |
| `OrderCode` | `string` | Mã đơn hàng nội bộ |
| `PartnerCode` | `string` | Mã vận đơn đối tác (GHN, GHTK, VNPost...) |
| `AppliedCodAmount` | `decimal` | Số tiền COD thu hộ (VNĐ) |
| `AppliedFeeAmount` | `decimal` | Tổng phí vận chuyển (VNĐ) |
| `NetAmount` | `decimal` | Số tiền ròng = AppliedCodAmount - AppliedFeeAmount (VNĐ) |
| `DeliveredDate` | `string` (ISO 8601) / `null` | Ngày giao hàng thành công |
| `CodCollectedDate` | `string` (ISO 8601) / `null` | Ngày thu COD |
| `ChargeWeight` | `decimal` | Trọng lượng tính cước (kg) |
| `ShippingFee` | `decimal` | Phí vận chuyển cơ bản (VNĐ) |
| `DeliveryAddress` | `string` | Địa chỉ giao hàng |
| `ReceiverName` | `string` | Tên người nhận |
| `ReceiverPhone` | `string` | Số điện thoại người nhận |
| `Note` | `string` | Ghi chú đơn hà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

---

### 500 Internal Server Error - Khách hàng không tồn tại
```json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.6.1",
  "title": "An error occurred while processing your request.",
  "status": 500,
  "detail": "Khách hàng không tồn tại",
  "traceId": "00-xxxxx"
}
```

**Nguyên nhân:** `customerId` không tồn tại trong hệ thống

---

### 500 Internal Server Error - Không có quyền truy cập
```json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.6.1",
  "title": "An error occurred while processing your request.",
  "status": 500,
  "detail": "Bạn không có quyền truy cập dữ liệu khách hàng này",
  "traceId": "00-xxxxx"
}
```

**Nguyên nhân:** User không phải Founder/Kế toán và không phải là người quản lý khách hàng này

---

## Business Logic

### Quy tắc lọc đơn hàng:

1. **Chỉ lấy đơn thuộc khách hàng được chỉ định:**
   - Đơn hàng phải có `CustomerId` khớp với tham số `customerId`

2. **Chỉ lấy đơn đã hoàn thành:**
   - Trạng thái đơn hàng phải là `Delivered` (Đã giao) hoặc `Returned` (Đã hoàn)

3. **Loại trừ đơn đã nằm trong phiếu đối soát:**
   - Những đơn hàng đã có trong `CustomerReconciliationSlipDetail` của phiếu chưa bị từ chối (Status != Rejected) sẽ bị loại ra
   - Đơn hàng trong phiếu bị từ chối (Rejected) sẽ được "giải phóng" và xuất hiện lại trong danh sách

### Phân quyền:

| Role | Phạm vi truy cập |
|------|------------------|
| `Founder` | Xem tất cả khách hàng |
| `CustomerReconciliationSlip.View` (Kế toán) | Xem tất cả khách hàng |
| Sale (mặc định) | Chỉ xem khách hàng do mình quản lý (`customer.ManagerId == userId`) |

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

- **AppliedCodAmount**: Lấy từ `OrderFin.CollectAmount` - số tiền COD thu hộ
- **AppliedFeeAmount**: Lấy từ `OrderFin.TotalFee` - tổng phí vận chuyển
- **NetAmount**: `AppliedCodAmount - AppliedFeeAmount` - số tiền thực tế cần thanh toán cho/từ khách
  - Nếu `NetAmount > 0`: Công ty nợ khách (cần trả khách)
  - Nếu `NetAmount < 0`: Khách nợ công ty (đơn hoàn chưa thu được cước)

---

## Ví dụ sử dụng

### Example 1: Lấy danh sách đơn có thể đối soát của khách hàng

**Request:**
```bash
curl -X GET "https://api.yourdomain.com/api/CustomerReconciliationSlip/GetReconciliableOrders?customerId=125" \
  -H "Authorization: Bearer {your_token}" \
  -H "Content-Type: application/json"
```

**Response:**
```json
[
  {
    "Id": 0,
    "CustomerReconciliationSlipId": 0,
    "OrderId": 10523,
    "OrderCode": "LD2411280001",
    "PartnerCode": "GHN123456",
    "AppliedCodAmount": 500000,
    "AppliedFeeAmount": 25000,
    "NetAmount": 475000,
    "DeliveredDate": "2024-11-28T14:30:00+07:00",
    "CodCollectedDate": "2024-11-29T10:00:00+07:00",
    "ChargeWeight": 1.5,
    "ShippingFee": 25000,
    "DeliveryAddress": "123 Đường ABC, Phường 1, Quận 1, TP.HCM",
    "ReceiverName": "Nguyễn Văn A",
    "ReceiverPhone": "0901234567",
    "Note": "Giao hàng buổi sáng"
  }
]
```

---

### Example 2: Trường hợp không có đơn hàng nào

Khi khách hàng không có đơn hàng nào có thể đối soát (đã được đối soát hết hoặc không có đơn completed):

**Request:**
```bash
curl -X GET "https://api.yourdomain.com/api/CustomerReconciliationSlip/GetReconciliableOrders?customerId=999" \
  -H "Authorization: Bearer {your_token}" \
  -H "Content-Type: application/json"
```

**Response:**
```json
[]
```

---

### Example 3: Frontend integration (JavaScript)

```javascript
// Lấy danh sách đơn hàng có thể đối soát
async function getReconciliableOrders(customerId) {
  try {
    const response = await fetch(
      `/api/CustomerReconciliationSlip/GetReconciliableOrders?customerId=${customerId}`,
      {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${getAccessToken()}`,
          'Content-Type': 'application/json'
        }
      }
    );

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

    const orders = await response.json();
    
    // Tính tổng
    const summary = {
      totalOrders: orders.length,
      totalCodAmount: orders.reduce((sum, o) => sum + o.AppliedCodAmount, 0),
      totalFeeAmount: orders.reduce((sum, o) => sum + o.AppliedFeeAmount, 0),
      netAmount: orders.reduce((sum, o) => sum + o.NetAmount, 0)
    };
    
    return { orders, summary };
  } catch (error) {
    console.error('Failed to fetch reconciliable orders:', error);
    throw error;
  }
}

// Sử dụng
const { orders, summary } = await getReconciliableOrders(125);
console.log(`Có ${summary.totalOrders} đơn có thể đối soát`);
console.log(`Tổng COD: ${summary.totalCodAmount.toLocaleString('vi-VN')} ₫`);
console.log(`Tổng phí: ${summary.totalFeeAmount.toLocaleString('vi-VN')} ₫`);
console.log(`Số tiền ròng: ${summary.netAmount.toLocaleString('vi-VN')} ₫`);
```

---

## TypeScript Interface

```typescript
interface CustomerReconciliationSlipDetailItem {
  Id: number;
  CustomerReconciliationSlipId: number;
  OrderId: number;
  OrderCode: string;
  PartnerCode: string;
  AppliedCodAmount: number;
  AppliedFeeAmount: number;
  NetAmount: number;
  DeliveredDate: string | null;
  CodCollectedDate: string | null;
  ChargeWeight: number;
  ShippingFee: number;
  DeliveryAddress: string;
  ReceiverName: string;
  ReceiverPhone: string;
  Note: string;
}

// API Response
type GetReconciliableOrdersResponse = CustomerReconciliationSlipDetailItem[];
```

---

## Notes

- **Định dạng thời gian:** Sử dụng ISO 8601 với timezone (`2024-11-28T14:30:00+07:00`)
- **Định dạng tiền tệ:** Đơn vị là VNĐ, không format
- **Trường hợp đơn hoàn:** 
  - `AppliedCodAmount = 0` (không thu được tiền)
  - `AppliedFeeAmount > 0` (vẫn tính cước)
  - `NetAmount < 0` (khách nợ công ty tiền cước)
- **Security:** API yêu cầu Bearer Token hợp lệ và role phù hợp
- **Performance:** Query sử dụng Include để load các navigation properties cần thiết (OrderFin, OrderOps, Addresses)

---

## Workflow

API này thường được sử dụng trong quy trình tạo phiếu đối soát:

1. **Bước 1:** Gọi `GetCustomersOverview` để lấy danh sách khách hàng cần đối soát
2. **Bước 2:** Khi chọn một khách hàng, gọi `GetReconciliableOrders` để lấy danh sách đơn hàng
3. **Bước 3:** Cho phép user chọn các đơn hàng cần đối soát
4. **Bước 4:** Gọi `Create` với danh sách `OrderIds` đã chọn để tạo phiếu

---

## Change Log

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

---

## Related APIs

- `POST /api/CustomerReconciliationSlip/GetCustomersOverview` - Lấy danh sách khách hàng cần đối soát
- `POST /api/CustomerReconciliationSlip/Create` - Tạo phiếu đối soát
- `GET /api/CustomerReconciliationSlip/GetById/{id}` - Lấy chi tiết phiếu đối soát
- `POST /api/CustomerReconciliationSlip/Search` - Tìm kiếm phiếu đối soát đã tạo
