# API Documentation - Order Risk Monitoring (No Dest Cashier)

## Tổng quan

Hệ thống API giám sát rủi ro đơn hàng thiếu DestCashier cung cấp 2 endpoints chính:
1. **Overview API**: Hiển thị tổng quan số liệu (dashboard/cards)
2. **Detail List API**: Hiển thị danh sách chi tiết đơn hàng có rủi ro

## Workflow Frontend

```mermaid
graph LR
    A[Dashboard] -->|Hiển thị| B[Overview API]
    B -->|Click vào số liệu| C[Detail List API]
    C -->|Hiển thị| D[Modal/Page với danh sách đơn]
```

**Use Case:**
1. Frontend gọi **Overview API** để hiển thị cards/metrics trên dashboard
2. User click vào số liệu (ví dụ: "120 đơn thiếu DestCashier")
3. Frontend gọi **Detail List API** với cùng filter (fromDate, toDate)
4. Hiển thị danh sách chi tiết trong modal popup hoặc navigate sang trang mới

---

## 1. Overview API

### GET `/api/OrderRisk/NoDestCashier/Overview`

**Mục đích:** Lấy tổng quan số đơn hàng có PayFeeType=2 (thu cước người nhận), đã giao thành công nhưng thiếu DestCashierId

**Use Case Frontend:**
- Hiển thị trên dashboard dạng cards/metrics
- Hiển thị số liệu tổng quan để user biết có bao nhiêu đơn cần xử lý
- **Khi click vào card/số liệu này → gọi Detail List API**

### Request

**Query Parameters:**

| Tham số | Kiểu | Bắt buộc | Mô tả |
|---------|------|----------|-------|
| fromDate | DateTime | Không | Lọc từ ngày (format: `YYYY-MM-DD` hoặc `YYYY-MM-DDTHH:mm:ss`) |
| toDate | DateTime | Không | Lọc đến ngày (format: `YYYY-MM-DD` hoặc `YYYY-MM-DDTHH:mm:ss`) |

**Example:**
```http
GET /api/OrderRisk/NoDestCashier/Overview?fromDate=2025-01-01&toDate=2025-01-31
```

### Response

**Success (200 OK):**

```json
{
  "TotalOrders": 120,
  "TotalDestFee": 36000000,
  "TotalFee": 60000000,
  "RiskInfo": {
    "Code": "NO_DEST_CASHIER",
    "Name": "Đơn thu người nhận thiếu thông tin Cashier",
    "Description": "Đơn hàng có PayFeeType = ReceiverPay (thu người nhận), đã thành công nhưng không có DestFeeCashierId",
    "Severity": "Critical",
    "Solution": "Cập nhật DestFeeCashierId cho các đơn hàng này hoặc thay đổi PayFeeType"
  }
}
```

**Response Fields:**

| Field | Kiểu | Mô tả |
|-------|------|-------|
| TotalOrders | int | Tổng số đơn hàng có rủi ro |
| TotalDestFee | decimal | Tổng tiền phí giao hàng (DestFee) |
| TotalFee | decimal | Tổng phí tổng cộng (TotalFee) |
| RiskInfo.Code | string | Mã định danh loại rủi ro |
| RiskInfo.Name | string | Tên hiển thị của loại rủi ro |
| RiskInfo.Description | string | Mô tả chi tiết về rủi ro |
| RiskInfo.Severity | string | Mức độ nghiêm trọng: Low, Medium, High, Critical |
| RiskInfo.Solution | string | Hướng giải quyết |

**Error (400 Bad Request):**
```json
{
  "message": "Error message here"
}
```

### Frontend Implementation Example

**Hiển thị Dashboard Card:**

```vue
<template>
  <div class="risk-card critical" @click="showDetailList">
    <div class="card-header">
      <h3>{{ overview.riskInfo.name }}</h3>
      <span class="severity-badge">{{ overview.riskInfo.severity }}</span>
    </div>
    <div class="card-metrics">
      <div class="metric">
        <span class="value">{{ overview.totalOrders }}</span>
        <span class="label">Đơn hàng</span>
      </div>
      <div class="metric">
        <span class="value">{{ formatCurrency(overview.totalDestFee) }}</span>
        <span class="label">Phí giao hàng</span>
      </div>
      <div class="metric">
        <span class="value">{{ formatCurrency(overview.totalFee) }}</span>
        <span class="label">Tổng phí</span>
      </div>
    </div>
    <div class="card-footer">
      <span>Click để xem chi tiết →</span>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      overview: null,
      filters: {
        fromDate: '2025-01-01',
        toDate: '2025-01-31'
      }
    }
  },
  methods: {
    async loadOverview() {
      const response = await fetch(
        `/api/OrderRisk/NoDestCashier/Overview?fromDate=${this.filters.fromDate}&toDate=${this.filters.toDate}`
      );
      this.overview = await response.json();
    },
    showDetailList() {
      // Option 1: Mở modal popup
      this.$refs.detailModal.open(this.filters);
      
      // Option 2: Navigate sang trang mới
      this.$router.push({
        name: 'OrderRiskNoDestCashierDetail',
        query: this.filters
      });
    }
  }
}
</script>
```

---

## 2. Detail List API

### GET `/api/OrderRisk/NoDestCashier`

**Mục đích:** Lấy danh sách chi tiết các đơn hàng có PayFeeType=2, đã giao thành công nhưng thiếu DestCashierId (có phân trang)

**Use Case Frontend:**
- Hiển thị danh sách chi tiết khi user click vào số liệu từ Overview
- Có thể hiển thị trong modal popup hoặc trang riêng
- Hỗ trợ phân trang để xử lý số lượng lớn đơn hàng

### Request

**Query Parameters:**

| Tham số | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---------|------|----------|----------|-------|
| fromDate | DateTime | Không | - | Lọc từ ngày |
| toDate | DateTime | Không | - | Lọc đến ngày |
| page | int | Không | 1 | Trang hiện tại (bắt đầu từ 1) |
| pageSize | int | Không | 50 | Số bản ghi mỗi trang (tối đa 100) |

**Example:**
```http
GET /api/OrderRisk/NoDestCashier?fromDate=2025-01-01&toDate=2025-01-31&page=1&pageSize=50
```

### Response

**Success (200 OK):**

```json
{
  "TotalRecords": 120,
  "PageNumber": 1,
  "PageSize": 50,
  "TotalPages": 3,
  "Orders": [
    {
      "Id": 12345,
      "Code": "LD2025010001",
      "CreatedTime": "2025-01-05T10:30:00+07:00",
      "CustomerId": 678,
      "PayFeeType": 2,
      "Status": 55,
      "PhysicalStatus": 55,
      "DestFee": 25000,
      "TotalFee": 45000,
      "DestFeeCashierId": 0,
      "DestFeeCashierUsername": null,
      "DestFeeStage": 1,
      "CustomerName": "Công ty XYZ",
      "CustomerPhone": "0912345678",
      "ReceiverName": "Trần Thị B",
      "ReceiverPhone": "0987654321",
      "ReceiverAddress": "456 Đường XYZ, Quận 2, TP.HCM"
    }
    // ... 49 more orders
  ],
  "RiskInfo": {
    "Code": "NO_DEST_CASHIER",
    "Name": "Đơn thu người nhận thiếu thông tin Cashier",
    "Description": "Đơn hàng có PayFeeType = ReceiverPay (thu người nhận), đã thành công nhưng không có DestFeeCashierId",
    "Severity": "Critical",
    "Solution": "Cập nhật DestFeeCashierId cho các đơn hàng này hoặc thay đổi PayFeeType"
  }
}
```

**Response Fields:**

| Field | Kiểu | Mô tả |
|-------|------|-------|
| TotalRecords | int | Tổng số bản ghi |
| PageNumber | int | Trang hiện tại |
| PageSize | int | Số bản ghi mỗi trang |
| TotalPages | int | Tổng số trang |
| Orders | array | Danh sách đơn hàng (xem chi tiết bên dưới) |
| RiskInfo | object | Thông tin về loại rủi ro |

**Order Object Fields:**

| Field | Kiểu | Mô tả |
|-------|------|-------|
| Id | int | ID đơn hàng |
| Code | string | Mã đơn hàng |
| CreatedTime | DateTime | Ngày tạo đơn |
| CustomerId | int | ID khách hàng |
| PayFeeType | int | Loại thanh toán (2=ReceiverPay) |
| Status | int | Trạng thái đơn hàng (55=Delivered) |
| PhysicalStatus | int | Trạng thái vật lý (55=Delivered) |
| DestFee | decimal | Phí giao hàng |
| TotalFee | decimal | Tổng phí |
| DestFeeCashierId | int | ID người thu phí giao hàng (0 = thiếu thông tin) |
| DestFeeCashierUsername | string? | Username người thu phí giao hàng |
| DestFeeStage | int | Giai đoạn thu phí giao hàng |
| CustomerName | string? | Tên khách hàng |
| CustomerPhone | string? | SĐT khách hàng |
| ReceiverName | string? | Tên người nhận |
| ReceiverPhone | string? | SĐT người nhận |
| ReceiverAddress | string? | Địa chỉ người nhận |

**Error (400 Bad Request):**
```json
{
  "message": "Error message here"
}
```

### Frontend Implementation Example

**Option 1: Modal Popup**

```vue
<template>
  <!-- Dashboard Card từ Overview -->
  <div class="risk-card" @click="openDetailModal">
    <h3>{{ overview.totalOrders }} đơn thiếu DestCashier</h3>
  </div>

  <!-- Modal hiển thị danh sách chi tiết -->
  <el-dialog 
    v-model="showModal" 
    title="Danh sách đơn hàng có rủi ro"
    width="80%"
  >
    <div class="risk-detail">
      <div class="filters">
        <el-date-picker v-model="filters.fromDate" type="date" />
        <el-date-picker v-model="filters.toDate" type="date" />
        <el-button @click="loadDetailList">Lọc</el-button>
      </div>

      <el-table :data="detailList.orders" border>
        <el-table-column prop="code" label="Mã đơn" width="150" />
        <el-table-column prop="createdTime" label="Ngày tạo" width="180" />
        <el-table-column prop="customerName" label="Khách hàng" />
        <el-table-column prop="receiverName" label="Người nhận" />
        <el-table-column prop="destFee" label="Phí giao" width="120">
          <template #default="scope">
            {{ formatCurrency(scope.row.destFee) }}
          </template>
        </el-table-column>
        <el-table-column prop="totalFee" label="Tổng phí" width="120">
          <template #default="scope">
            {{ formatCurrency(scope.row.totalFee) }}
          </template>
        </el-table-column>
        <el-table-column label="Hành động" width="150">
          <template #default="scope">
            <el-button size="small" @click="viewOrder(scope.row.id)">
              Xem chi tiết
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <el-pagination
        v-model:current-page="filters.page"
        v-model:page-size="filters.pageSize"
        :total="detailList.totalRecords"
        @current-change="loadDetailList"
        @size-change="loadDetailList"
      />
    </div>
  </el-dialog>
</template>

<script>
export default {
  data() {
    return {
      showModal: false,
      overview: null,
      detailList: {
        orders: [],
        totalRecords: 0,
        totalPages: 0
      },
      filters: {
        fromDate: '2025-01-01',
        toDate: '2025-01-31',
        page: 1,
        pageSize: 50
      }
    }
  },
  methods: {
    async loadOverview() {
      const response = await fetch(
        `/api/OrderRisk/NoDestCashier/Overview?fromDate=${this.filters.fromDate}&toDate=${this.filters.toDate}`
      );
      this.overview = await response.json();
    },
    
    async openDetailModal() {
      this.showModal = true;
      await this.loadDetailList();
    },
    
    async loadDetailList() {
      const params = new URLSearchParams({
        fromDate: this.filters.fromDate,
        toDate: this.filters.toDate,
        page: this.filters.page,
        pageSize: this.filters.pageSize
      });
      
      const response = await fetch(
        `/api/OrderRisk/NoDestCashier?${params}`
      );
      this.detailList = await response.json();
    },
    
    viewOrder(orderId) {
      // Navigate to order detail page
      this.$router.push(`/orders/${orderId}`);
    }
  },
  mounted() {
    this.loadOverview();
  }
}
</script>
```

**Option 2: Navigate to Separate Page**

```vue
<!-- Dashboard Component -->
<template>
  <div class="risk-card" @click="navigateToDetail">
    <h3>{{ overview.totalOrders }} đơn thiếu DestCashier</h3>
  </div>
</template>

<script>
export default {
  methods: {
    navigateToDetail() {
      this.$router.push({
        name: 'OrderRiskNoDestCashierDetailList',
        query: {
          fromDate: this.filters.fromDate,
          toDate: this.filters.toDate
        }
      });
    }
  }
}
</script>

<!-- OrderRiskNoDestCashierDetailList.vue - Separate Page -->
<template>
  <div class="risk-detail-page">
    <h1>{{ detailList.riskInfo.name }}</h1>
    <p>{{ detailList.riskInfo.description }}</p>
    
    <!-- Filters and Table (same as modal) -->
  </div>
</template>

<script>
export default {
  mounted() {
    // Get filters from route query
    this.filters.fromDate = this.$route.query.fromDate;
    this.filters.toDate = this.$route.query.toDate;
    this.loadDetailList();
  }
}
</script>
```

---

## Complete Workflow Example

### 1. Initial Load - Dashboard

```javascript
// Component: RiskDashboard.vue
async mounted() {
  // Load overview data
  const response = await fetch('/api/OrderRisk/NoDestCashier/Overview');
  this.overview = await response.json();
  
  // Display in card:
  // "120 đơn thiếu DestCashier"
  // "Tổng phí giao: 36,000,000 VNĐ"
}
```

### 2. User Clicks on Card

```javascript
// User clicks on the card with 120 orders
async showDetail() {
  // Open modal or navigate
  this.showModal = true;
  
  // Load detail list with same filters
  const params = new URLSearchParams({
    fromDate: this.filters.fromDate,
    toDate: this.filters.toDate,
    page: 1,
    pageSize: 50
  });
  
  const response = await fetch(
    `/api/OrderRisk/NoDestCashier?${params}`
  );
  this.detailList = await response.json();
  
  // Display table with 50 orders (page 1 of 3)
}
```

### 3. Pagination

```javascript
// User clicks "Next Page"
async goToPage(page) {
  this.filters.page = page;
  
  const params = new URLSearchParams({
    fromDate: this.filters.fromDate,
    toDate: this.filters.toDate,
    page: this.filters.page,
    pageSize: this.filters.pageSize
  });
  
  const response = await fetch(
    `/api/OrderRisk/NoDestCashier?${params}`
  );
  this.detailList = await response.json();
}
```

---

## Best Practices

### 1. Caching Strategy
```javascript
// Cache overview data for 5 minutes
const CACHE_DURATION = 5 * 60 * 1000;
let cachedOverview = null;
let cacheTime = null;

async function getOverview() {
  const now = Date.now();
  if (cachedOverview && (now - cacheTime) < CACHE_DURATION) {
    return cachedOverview;
  }
  
  const response = await fetch('/api/OrderRisk/NoDestCashier/Overview');
  cachedOverview = await response.json();
  cacheTime = now;
  return cachedOverview;
}
```

### 2. Error Handling
```javascript
async function loadDetailList() {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    this.detailList = await response.json();
  } catch (error) {
    this.$message.error('Không thể tải danh sách đơn hàng: ' + error.message);
  }
}
```

### 3. Loading States
```vue
<template>
  <div v-loading="loading">
    <div v-if="overview">
      <!-- Display overview -->
    </div>
  </div>
</template>

<script>
data() {
  return {
    loading: false,
    overview: null
  }
},
async mounted() {
  this.loading = true;
  try {
    this.overview = await this.loadOverview();
  } finally {
    this.loading = false;
  }
}
</script>
```

---

## Notes

- **Filter Consistency**: Luôn truyền cùng `fromDate` và `toDate` từ Overview sang Detail List để đảm bảo số liệu nhất quán
- **Pagination**: Detail List hỗ trợ phân trang, nên xử lý khi số lượng đơn lớn
- **Real-time Updates**: Nếu cần, có thể add refresh button để cập nhật số liệu mới nhất
- **Authorization**: Cả 2 endpoints đều yêu cầu authentication (header `Authorization: Bearer {token}`)
- **Điều kiện lọc**: API chỉ lấy đơn hàng có `PayFeeType = 2` (ReceiverPay), `Status = 55` (Delivered), `PhysicalStatus = 55` (Delivered) và không có `DestFeeCashierId`


