# API Quản Lý Vùng Phủ Chi Nhánh (Agency Location)

## Tổng quan

API này cung cấp các chức năng để quản lý vùng phủ (coverage area) của chi nhánh, bao gồm:

- Lấy thông tin vùng phủ của chi nhánh
- Cập nhật danh sách vùng phủ cho chi nhánh

## 1. API: Lấy thông tin vùng phủ của chi nhánh

- **Service**: IAgencyLocationService.GetAgencyCoverage(int agencyId)
- **Controller**: AgencyLocationController
- **Route**: GET /api/AgencyLocation/GetAgencyCoverage
- **Authorization**: [Authorize(Roles = "AgencyLocation.GetAgencyCoverage")]
- **Mục đích**: Lấy danh sách các location (địa điểm) trong vùng phủ của một chi nhánh

### Mô tả

Endpoint này trả về thông tin vùng phủ của một chi nhánh, bao gồm danh sách tất cả các location (tỉnh/quận/phường) mà chi nhánh đó phục vụ.

### Request

- **Method**: GET
- **URL**: /api/AgencyLocation/GetAgencyCoverage?agencyId={agencyId}
- **Query Parameters** (PascalCase):
  - `agencyId` (int, required): ID của chi nhánh cần lấy thông tin vùng phủ
- **Header**: Authorization: Bearer <jwt-token> (yêu cầu quyền AgencyLocation.GetAgencyCoverage)

#### Ví dụ Request:

```
GET /api/AgencyLocation/GetAgencyCoverage?agencyId=5
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

### Response

- **HTTP 200 OK**
- **Content-Type**: application/json

#### Cấu trúc Response (PascalCase):

```json
{
  "AgencyId": 5,
  "Locations": [
    {
      "Id": 1,
      "Name": "Hà Nội",
      "Description": "Thành phố Hà Nội",
      "DisplayOrder": 1,
      "ParentId": 0,
      "City": "Hà Nội",
      "District": "",
      "Level": 1,
      "Type": "Nội thành",
      "Prefix": "Thành phố",
      "Lat": 21.0285,
      "Long": 105.8542,
      "CreatedTime": "01/01/2024 10:00",
      "UpdatedTime": "01/01/2024 10:00",
      "CreatedBy": "admin",
      "UpdatedBy": "admin"
    },
    {
      "Id": 2,
      "Name": "Ba Đình",
      "Description": "Quận Ba Đình",
      "DisplayOrder": 2,
      "ParentId": 1,
      "City": "Hà Nội",
      "District": "Ba Đình",
      "Level": 2,
      "Type": "Nội thành",
      "Prefix": "Quận",
      "Lat": 21.0333,
      "Long": 105.8189,
      "CreatedTime": "01/01/2024 10:00",
      "UpdatedTime": "01/01/2024 10:00",
      "CreatedBy": "admin",
      "UpdatedBy": "admin"
    }
  ]
}
```

#### Các trường trong LocationItem:

| Tên trường   | Kiểu dữ liệu | Mô tả                                                   |
| ------------ | ------------ | ------------------------------------------------------- |
| Id           | int          | ID của location                                         |
| Name         | string       | Tên location                                            |
| Description  | string       | Mô tả location                                          |
| DisplayOrder | int          | Thứ tự hiển thị                                         |
| ParentId     | int          | ID của location cha                                     |
| City         | string       | Tên tỉnh/thành phố                                      |
| District     | string       | Tên quận/huyện                                          |
| Level        | byte         | Cấp độ (1: Tỉnh/Thành phố, 2: Quận/Huyện, 3: Phường/Xã) |
| Type         | string       | Loại vùng phủ (Nội thành, Ngoại thành, Không cung cấp)  |
| Prefix       | string       | Tiền tố (Thành phố, Tỉnh, Quận, Huyện, Phường, Xã)      |
| Lat          | double       | Vĩ độ                                                   |
| Long         | double       | Kinh độ                                                 |
| CreatedTime  | string       | Thời gian tạo (format: dd/MM/yyyy HH:mm)                |
| UpdatedTime  | string       | Thời gian cập nhật (format: dd/MM/yyyy HH:mm)           |
| CreatedBy    | string       | Người tạo                                               |
| UpdatedBy    | string       | Người cập nhật                                          |

### Error Response

#### BadRequest (400) - AgencyId không hợp lệ:

```json
"AgencyId không hợp lệ"
```

#### Unauthorized (401) - Không có quyền truy cập:

```json
{
  "message": "Unauthorized"
}
```

---

## 2. API: Thêm location vào chi nhánh

- **Service**: IAgencyLocationService.AddLocationToAgency(AddLocationsToAgencyModel model)
- **Controller**: AgencyLocationController
- **Route**: POST /api/AgencyLocation/AddLocation
- **Authorization**: [Authorize(Roles = "AgencyLocation.AddLocation")]
- **Mục đích**: Thêm một hoặc nhiều location vào vùng phủ của chi nhánh

### Mô tả

Endpoint này thêm location vào vùng phủ của chi nhánh.

- **CHỈ thêm location mới**, không ảnh hưởng đến location đã có
- **CHỈ chấp nhận phường/xã (Level 3)**
- Location phải chưa thuộc agency nào khác

### Request

- **Method**: POST
- **URL**: /api/AgencyLocation/AddLocation
- **Body** (JSON, PascalCase):

```json
{
  "AgencyId": 5,
  "LocationIds": [1, 2, 3, 10, 15, 20]
}
```

#### Cấu trúc Request Body:

| Tên trường  | Kiểu dữ liệu    | Bắt buộc | Mô tả                                                          |
| ----------- | --------------- | -------- | -------------------------------------------------------------- |
| AgencyId    | int             | Yes      | ID của chi nhánh cần thêm location                             |
| LocationIds | List&lt;int&gt; | Yes      | Danh sách ID của các location cần thêm (chỉ phường/xã Level 3) |

- **Header**: Authorization: Bearer <jwt-token> (yêu cầu quyền AgencyLocation.AddLocation)

#### Ví dụ Request:

```http
POST /api/AgencyLocation/AddLocation
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

{
  "AgencyId": 5,
  "LocationIds": [1, 2, 3, 10, 15, 20]
}
```

### Response

- **HTTP 200 OK**
- **Content-Type**: application/json

#### Success Response:

```json
{
  "Result": 1,
  "Message": "Đã thêm 6 location vào đại lý thành công"
}
```

#### Cấu trúc BaseResult:

| Tên trường | Kiểu dữ liệu | Mô tả                                           |
| ---------- | ------------ | ----------------------------------------------- |
| Result     | int          | Kết quả (1: Success, 2: Failed, 3: SystemError) |
| Message    | string       | Thông báo kết quả                               |

### Error Response

#### BadRequest (400) - ModelState không hợp lệ:

```json
{
  "errors": {
    "AgencyId": ["The AgencyId field is required."]
  }
}
```

#### BadRequest (400) - AgencyId không hợp lệ:

```json
"AgencyId không hợp lệ"
```

#### BadRequest (400) - Không xác định được người dùng:

```json
"Không xác định được người dùng"
```

#### Failed Response - Không tìm thấy đại lý:

```json
{
  "Result": 2,
  "Message": "Không tìm thấy đại lý"
}
```

#### Failed Response - Một số location không tồn tại:

```json
{
  "Result": 2,
  "Message": "Một số location không tồn tại"
}
```

#### Failed Response - Location không phải Level 3:

```json
{
  "Result": 2,
  "Message": "Chỉ được phép gán phường/xã (Level 3). Phát hiện 2 location không hợp lệ: Quận 1, Hồ Chí Minh"
}
```

#### Failed Response - Location đã thuộc agency khác:

```json
{
  "Result": 2,
  "Message": "Một số location đã thuộc agency khác: Bến Nghé (Agency 2)"
}
```

#### Failed Response - Location đã thuộc agency này:

```json
{
  "Result": 2,
  "Message": "Một số location đã thuộc agency này: Bến Nghé"
}
```

#### SystemError Response - Lỗi hệ thống:

```json
{
  "Result": 3,
  "Message": "Chi tiết lỗi từ exception"
}
```

#### Unauthorized (401) - Không có quyền truy cập:

```json
{
  "message": "Unauthorized"
}
```

---

## 3. API: Xóa location khỏi chi nhánh

- **Service**: IAgencyLocationService.RemoveLocationFromAgency(AddLocationsToAgencyModel model)
- **Controller**: AgencyLocationController
- **Route**: POST /api/AgencyLocation/RemoveLocation
- **Authorization**: [Authorize(Roles = "AgencyLocation.RemoveLocation")]
- **Mục đích**: Xóa một hoặc nhiều location khỏi vùng phủ của chi nhánh

### Mô tả

Endpoint này xóa location khỏi vùng phủ của chi nhánh bằng cách set AgencyId = 0.

- **CHỈ xóa location được chỉ định**, không ảnh hưởng đến location khác
- Location phải thuộc agency này mới có thể xóa

### Request

- **Method**: POST
- **URL**: /api/AgencyLocation/RemoveLocation
- **Body** (JSON, PascalCase):

```json
{
  "AgencyId": 5,
  "LocationIds": [1, 2, 3]
}
```

#### Cấu trúc Request Body:

| Tên trường  | Kiểu dữ liệu    | Bắt buộc | Mô tả                                 |
| ----------- | --------------- | -------- | ------------------------------------- |
| AgencyId    | int             | Yes      | ID của chi nhánh cần xóa location     |
| LocationIds | List&lt;int&gt; | Yes      | Danh sách ID của các location cần xóa |

- **Header**: Authorization: Bearer <jwt-token> (yêu cầu quyền AgencyLocation.RemoveLocation)

#### Ví dụ Request:

```http
POST /api/AgencyLocation/RemoveLocation
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

{
  "AgencyId": 5,
  "LocationIds": [1, 2, 3]
}
```

### Response

- **HTTP 200 OK**
- **Content-Type**: application/json

#### Success Response:

```json
{
  "Result": 1,
  "Message": "Đã xóa 3 location khỏi đại lý thành công"
}
```

#### Cấu trúc BaseResult:

| Tên trường | Kiểu dữ liệu | Mô tả                                           |
| ---------- | ------------ | ----------------------------------------------- |
| Result     | int          | Kết quả (1: Success, 2: Failed, 3: SystemError) |
| Message    | string       | Thông báo kết quả                               |

### Error Response

#### BadRequest (400) - ModelState không hợp lệ:

```json
{
  "errors": {
    "AgencyId": ["The AgencyId field is required."]
  }
}
```

#### BadRequest (400) - AgencyId không hợp lệ:

```json
"AgencyId không hợp lệ"
```

#### BadRequest (400) - Không xác định được người dùng:

```json
"Không xác định được người dùng"
```

#### Failed Response - Không tìm thấy đại lý:

```json
{
  "Result": 2,
  "Message": "Không tìm thấy đại lý"
}
```

#### Failed Response - Một số location không tồn tại:

```json
{
  "Result": 2,
  "Message": "Một số location không tồn tại"
}
```

#### Failed Response - Location không thuộc agency này:

```json
{
  "Result": 2,
  "Message": "Một số location không thuộc agency này: Bến Nghé (AgencyId: 2)"
}
```

#### SystemError Response - Lỗi hệ thống:

```json
{
  "Result": 3,
  "Message": "Chi tiết lỗi từ exception"
}
```

#### Unauthorized (401) - Không có quyền truy cập:

```json
{
  "message": "Unauthorized"
}
```

---

## Phụ thuộc & Liên quan

### Entities:

- **Lada.Framework/Data/Domains/Categories/AgencyLocation.cs**: Entity chính
- **Lada.Framework/Data/Domains/Categories/Agency.cs**: Entity đại lý
- **Lada.Framework/Data/Domains/Categories/Location.cs**: Entity địa điểm

### Entity Mappings:

- **Lada.Framework/Data/Mappings/EntityMappings/Categories/AgencyLocationMap.cs**: Cấu hình mapping với composite key (AgencyId, LocationId)

### DTOs:

- **Ladaer.BackEnd/Infrastructures/DTO/Models/Categories/AgencyLocationModel.cs**: Model mapping đơn
- **Ladaer.BackEnd/Infrastructures/DTO/Models/Categories/AddLocationsToAgencyModel.cs**: Model để thêm/cập nhật nhiều location
- **Ladaer.BackEnd/Infrastructures/DTO/Models/Categories/AgencyCoverageModel.cs**: Model trả về thông tin vùng phủ
- **Ladaer.BackEnd/Infrastructures/DTO/Items/Categories/LocationItem.cs**: Item hiển thị thông tin location

### Services & Controllers:

- **Ladaer.BackEnd/Infrastructures/Services/Categories/AgencyLocationService.cs**: Service xử lý business logic
- **Ladaer.BackEnd/Controllers/AgencyLocationController.cs**: Controller xử lý HTTP requests

### Database:

- **Table**: AgencyLocation
- **Primary Key**: Composite key (AgencyId, LocationId)
- **Foreign Keys**:
  - AgencyId → Agency.Id (ON DELETE CASCADE)
  - LocationId → Location.Id (ON DELETE CASCADE)
- **Indexes**:
  - DisplayOrder
  - LocationId

---

## Lưu ý quan trọng

### Về AddLocation và RemoveLocation:

1. **Thao tác đơn giản**: Chỉ thêm hoặc xóa location được chỉ định, không ảnh hưởng location khác
2. **Validation Level 3**: AddLocation chỉ chấp nhận phường/xã (Level 3)
3. **Kiểm tra ownership**: AddLocation kiểm tra location chưa thuộc agency nào khác
4. **Kiểm tra thuộc tính**: RemoveLocation kiểm tra location phải thuộc agency hiện tại
5. **Danh sách không rỗng**: Cả 2 API đều yêu cầu LocationIds phải có ít nhất 1 item

### Về Authorization:

- Cần có JWT token hợp lệ trong header Authorization
- User phải có role tương ứng:
  - `AgencyLocation.GetAgencyCoverage` để xem vùng phủ
  - `AgencyLocation.AddLocation` để thêm location
  - `AgencyLocation.RemoveLocation` để xóa location

### Về Audit:

- Thông tin người cập nhật (UpdatedBy, UpdatedByUserName) được tự động lấy từ JWT token
- Thời gian cập nhật (UpdatedTime) được tự động gán

---

## Use Cases

### Use Case 1: Xem vùng phủ của chi nhánh

```http
GET /api/AgencyLocation/GetAgencyCoverage?agencyId=5
Authorization: Bearer <token>
```

### Use Case 2: Thiết lập vùng phủ cho chi nhánh mới

```http
POST /api/AgencyLocation/AddLocation
Authorization: Bearer <token>

{
  "AgencyId": 5,
  "LocationIds": [1, 2, 3, 10, 15, 20, 25, 30]
}
```

### Use Case 3: Mở rộng vùng phủ (thêm location mới)

Chỉ cần gọi AddLocation với danh sách location mới:

```http
POST /api/AgencyLocation/AddLocation

{
  "AgencyId": 5,
  "LocationIds": [35, 40]  // Chỉ cần ID location mới, không cần lấy danh sách cũ
}
```

### Use Case 4: Thu hẹp vùng phủ (loại bỏ một số location)

```http
POST /api/AgencyLocation/RemoveLocation

{
  "AgencyId": 5,
  "LocationIds": [10, 15, 20]  // Xóa 3 location không còn phục vụ
}
```

### Use Case 5: Xóa toàn bộ vùng phủ

Bước 1: Lấy danh sách location hiện tại

```http
GET /api/AgencyLocation/GetAgencyCoverage?agencyId=5
```

Bước 2: Xóa tất cả location

```http
POST /api/AgencyLocation/RemoveLocation

{
  "AgencyId": 5,
  "LocationIds": [1, 2, 3, 10, 15, 20, 25, 30]  // Tất cả location từ bước 1
}
```

---

## Thay đổi gần đây

- **2024-10-24**: Refactor API thành AddLocation và RemoveLocation

  - Thay thế UpdateAgencyCoverage bằng 2 endpoint riêng biệt
  - AddLocation: Chỉ thêm location mới vào agency
  - RemoveLocation: Chỉ xóa location khỏi agency
  - Đơn giản hóa workflow cho người dùng

- **2024-10-23**: Tạo mới API quản lý vùng phủ chi nhánh
  - Sử dụng Location.AgencyId thay vì bảng AgencyLocation
  - Tạo service AgencyLocationService
  - Tạo controller AgencyLocationController
  - Hỗ trợ lazy loading với GetAvailableLocationsTree
