Bỏ qua

Use Case Implementation Plan & FE Integration Guide

Dành cho: Backend Team + Frontend Team
Mục đích: Kế hoạch implement API cho các use case chưa được tích hợp + Tài liệu hướng dẫn FE tích hợp
Ngày tạo: 2026-05-24


Tổng quan Gap Analysis

Trước khi lên kế hoạch, đây là kết quả phân tích những gì đã cóchưa có trong hệ thống:

Use Case Backend Gateway Exposed Ghi chú
CUSTOMER
Xem lịch trống của sân ✅ Exists ✅ Exposed FE chưa integrate
Thêm mới lịch đặt sân ✅ Exists ✅ Exposed FE chưa integrate
FIELD OWNER
Xem danh sách sân quản lý ✅ Exists ✅ Exposed FE chưa integrate
Cập nhật thông tin sân ✅ Exists ✅ Exposed FE chưa integrate
Cập nhật trạng thái sân ✅ Exists ✅ Exposed FE chưa integrate
Dashboard doanh thu ✅ Exists ✅ Exposed FE chưa integrate
ADMIN
Quản lý danh sách Người dùng ✅ user-service MISSING in gateway Cần thêm proxy
Quản lý Chủ sân & Đối tác ✅ Exists ✅ Exposed FE chưa integrate
Thống kê & báo cáo hệ thống MISSING MISSING Cần build mới
Tổng quan Admin Dashboard MISSING MISSING Cần build mới

Phần 1: Customer Use Cases


UC-01: Xem lịch trống của sân

1.1 Tại sao API này tồn tại?

Khách hàng cần xem slot giờ nào còn trống trước khi đặt sân. Hệ thống cung cấp hai dạng response:

  • Availability: Danh sách slot còn trống (dùng để hiển thị picker đơn giản)
  • Schedule Grid: Ma trận lịch đầy đủ với trạng thái BOOKED/AVAILABLE từng slot (dùng để render lịch dạng calendar)

1.2 Backend Status

Đã implement đầy đủbooking-service + Gateway proxy
Không cần implement thêm backend logic

1.3 API Specification

API 1: Xem danh sách slot còn trống

GET /bookings/fields/:fieldId/availability?date=YYYY-MM-DD
Auth Public (không cần JWT)
Path Param fieldId — UUID của sân
Query Params date (required): ngày cần xem, format YYYY-MM-DD
openTime (optional): giờ mở cửa HH:mm, default 08:00
closeTime (optional): giờ đóng cửa HH:mm, default 22:00

Response mẫu:

{
  "fieldId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "date": "2026-06-10",
  "operatingHours": { "open": "08:00", "close": "22:00" },
  "availableSlots": [
    {
      "startTime": "2026-06-10T08:00:00.000Z",
      "endTime": "2026-06-10T10:00:00.000Z"
    },
    {
      "startTime": "2026-06-10T12:00:00.000Z",
      "endTime": "2026-06-10T14:00:00.000Z"
    }
  ],
  "totalSlots": 2
}

API 2: Xem schedule grid (calendar view)

GET /bookings/fields/:fieldId/schedule-grid?date=YYYY-MM-DD
Auth Public (không cần JWT)
Path Param fieldId — UUID của sân
Query Params date (required): ngày cần xem
openTime (optional): giờ mở cửa, default 08:00
closeTime (optional): giờ đóng cửa, default 22:00
slotMinutes (optional): độ dài slot theo phút (15–180), default 60

Response mẫu:

{
  "fieldId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "date": "2026-06-10",
  "operatingHours": { "open": "08:00", "close": "22:00" },
  "slotMinutes": 60,
  "slots": [
    {
      "startTime": "2026-06-10T08:00:00.000Z",
      "endTime": "2026-06-10T09:00:00.000Z",
      "status": "AVAILABLE",
      "bookingCount": 0,
      "bookings": []
    },
    {
      "startTime": "2026-06-10T09:00:00.000Z",
      "endTime": "2026-06-10T10:00:00.000Z",
      "status": "BOOKED",
      "bookingCount": 1,
      "bookings": [
        {
          "id": "a1b2c3d4-...",
          "userId": "c3d4e5f6-...",
          "status": "CONFIRMED"
        }
      ]
    }
  ],
  "summary": {
    "totalSlots": 14,
    "bookedSlots": 3,
    "availableSlots": 11
  }
}

1.4 FE Integration Guide

Màn hình: Trang chi tiết sân (/fields/:fieldId)

Flow tích hợp:

User chọn ngày trên date picker
    → FE gọi GET /bookings/fields/:fieldId/schedule-grid?date={date}
    → Render calendar grid: slot màu xanh = AVAILABLE, màu đỏ = BOOKED
    → User click slot AVAILABLE
    → Highlight slot đã chọn, mở form đặt sân

Lưu ý khi integrate:

  • API này là public — không cần gắn Authorization header
  • startTime/endTime trong response là UTC ISO 8601 — FE cần convert sang local timezone để hiển thị
  • Nên gọi lại API sau khi user hoàn tất đặt sân để refresh lịch
  • Recommend dùng schedule-grid cho calendar view, dùng availability cho slot picker đơn giản

UC-02: Thêm mới lịch đặt sân

2.1 Tại sao API này tồn tại?

Sau khi chọn slot trống, khách hàng cần tạo booking và thanh toán. Quy trình gồm 3 bước: tạo booking (PENDING) → thanh toán → booking được CONFIRMED.

2.2 Backend Status

Đã implement đầy đủbooking-service + payment-service + Gateway
Không cần implement thêm backend logic

2.3 API Specification

Bước 1: Tạo booking

POST /bookings
Authorization: Bearer {jwt_token}

Request body:

{
  "fieldId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "startTime": "2026-06-10T08:00:00Z",
  "endTime": "2026-06-10T10:00:00Z",
  "notes": "Sinh nhật đội"
}

Response (201 Created):

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "fieldId": "b2c3d4e5-...",
  "userId": "c3d4e5f6-...",
  "startTime": "2026-06-10T08:00:00.000Z",
  "endTime": "2026-06-10T10:00:00.000Z",
  "totalPrice": 400000,
  "status": "PENDING",
  "notes": "Sinh nhật đội",
  "expiresAt": "2026-06-10T07:15:00.000Z",
  "createdAt": "2026-06-10T07:00:00.000Z"
}

⚠️ Booking tự động EXPIRED sau 15 phút nếu chưa thanh toán (xem expiresAt)


Bước 2: Xem danh sách booking của tôi

GET /bookings/me?page=1&limit=20&status=CONFIRMED
Authorization: Bearer {jwt_token}

Query params:

  • page (optional): default 1
  • limit (optional): default 20
  • status (optional): PENDING | CONFIRMED | CANCELLED | EXPIRED

Response (200 OK):

{
  "data": [
    {
      "id": "a1b2c3d4-...",
      "fieldId": "b2c3d4e5-...",
      "fieldName": "Sân bóng Thủ Đức",
      "startTime": "2026-06-10T08:00:00.000Z",
      "endTime": "2026-06-10T10:00:00.000Z",
      "totalPrice": 400000,
      "status": "CONFIRMED",
      "createdAt": "2026-06-01T10:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 5,
    "totalPages": 1
  }
}

Bước 3: Hủy booking

PUT /bookings/:id/cancel
Authorization: Bearer {jwt_token}

Request body (optional):

{ "reason": "Thay đổi kế hoạch" }

Response (200 OK):

{
  "id": "a1b2c3d4-...",
  "status": "CANCELLED",
  "cancelledAt": "2026-06-09T10:00:00.000Z"
}

2.4 FE Integration Guide

Màn hình: Trang đặt sân + Lịch sử đặt sân (/my-bookings)

Flow tích hợp:

UC-01: User chọn slot → FE gọi POST /bookings → nhận bookingId + totalPrice
    → FE redirect sang trang thanh toán với bookingId
    → Sau thanh toán thành công → booking status = CONFIRMED
    → FE redirect về /my-bookings

Trang My Bookings:
    → FE gọi GET /bookings/me → render danh sách
    → User click "Hủy" → FE gọi PUT /bookings/:id/cancel
    → Refresh list

Lưu ý khi integrate:

  • API tạo booking yêu cầu JWT token — phải đăng nhập trước
  • expiresAt trong response: FE nên hiển thị countdown timer cho PENDING booking
  • Error 409 Conflict: slot đã có người đặt — FE cần refresh lịch và thông báo user
  • Trường fieldName trong GET /bookings/me do gateway enrich từ field-service

Phần 2: Field Owner Use Cases


UC-03: Xem danh sách sân bóng quản lý

3.1 Tại sao API này tồn tại?

Field Owner cần có màn hình quản lý trung tâm — xem tất cả sân mình sở hữu cùng trạng thái (ACTIVE, PENDING_APPROVAL, CLOSED), từ đó navigate vào từng sân để quản lý chi tiết.

3.2 Backend Status

Đã implementGET /fields/owner/my-fields trong field-service + gateway

3.3 API Specification

GET /fields/owner/my-fields
Authorization: Bearer {jwt_token}   (Role: FIELD_OWNER)

Response (200 OK):

[
  {
    "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "name": "Sân bóng Thủ Đức A",
    "status": "ACTIVE"
  },
  {
    "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
    "name": "Sân mini Gò Vấp",
    "status": "PENDING_APPROVAL"
  }
]

Field status enum:

Status Mô tả
PENDING_APPROVAL Đang chờ admin duyệt
ACTIVE Đang mở, nhận đặt sân
CLOSED Chủ sân tắt tạm thời
INACTIVE Admin vô hiệu hóa
REJECTED Admin từ chối

3.4 FE Integration Guide

Màn hình: Owner Dashboard — /owner/fields

Flow tích hợp:

Owner đăng nhập → FE gọi GET /fields/owner/my-fields
    → Render danh sách dạng card:
      - Icon badge theo status (màu xanh/vàng/đỏ)
      - Button "Quản lý" → navigate to /owner/fields/:id
      - Button "Thêm sân" → navigate to /owner/fields/new

Ghi nhận quan trọng:

Response hiện chỉ trả về id, name, status (lightweight). Nếu FE cần hiển thị thêm số booking hôm nay hoặc doanh thu — cần gọi thêm:

  • GET /bookings/owner/bookings?fieldIds={id} cho booking count
  • GET /bookings/owner/revenue?startDate=...&endDate=... cho doanh thu

💡 Enhancement opportunity: Backend team có thể enhance GET /fields/owner/my-fields để trả thêm bookingCountTodaymonthlyRevenue nếu FE cần dashboard nhanh — nhưng hiện tại cần gọi 2 API riêng.


UC-04: Cập nhật thông tin sân bóng

4.1 Tại sao API này tồn tại?

Chủ sân cần tự quản lý thông tin sân (tên, mô tả, giá, tiện ích, ảnh, giờ hoạt động) mà không cần thông qua admin. Đây là core feature của owner self-service.

4.2 Backend Status

Đã implement đầy đủ — field-service + gateway

4.3 API Specification

API 1: Lấy chi tiết sân (để prefill form edit)

GET /fields/:id

Response: toàn bộ thông tin sân (public endpoint)


API 2: Cập nhật thông tin cơ bản

PUT /fields/:id
Authorization: Bearer {jwt_token}   (Role: FIELD_OWNER, phải là owner của sân)

Request body (tất cả fields đều optional):

{
  "name": "Sân bóng Thủ Đức A (nâng cấp)",
  "description": "Sân cỏ nhân tạo mới toanh, có mái che",
  "address": "123 Đường Lê Lợi, Quận 1, TP.HCM",
  "latitude": 10.7769,
  "longitude": 106.7009,
  "hourlyRate": 350000,
  "capacity": 14,
  "fieldSize": "FOOTBALL_7",
  "surfaceType": "ARTIFICIAL_GRASS",
  "amenities": ["PARKING", "SHOWER", "LIGHTING"]
}

Response (200 OK): Field object đã cập nhật


API 3: Cập nhật giờ hoạt động

PUT /fields/:id/operating-hours
Authorization: Bearer {jwt_token}   (Role: FIELD_OWNER)

Request body:

{
  "monday": { "open": "07:00", "close": "22:00" },
  "tuesday": { "open": "07:00", "close": "22:00" },
  "wednesday": { "open": "07:00", "close": "22:00" },
  "thursday": { "open": "07:00", "close": "22:00" },
  "friday": { "open": "07:00", "close": "23:00" },
  "saturday": { "open": "06:00", "close": "23:00" },
  "sunday": { "open": "06:00", "close": "22:00" }
}

API 4: Upload ảnh sân

POST /fields/:id/images
Authorization: Bearer {jwt_token}   (Role: FIELD_OWNER)
Content-Type: multipart/form-data

Form data: files[] — nhiều file ảnh (jpg/png, max 10MB/file)

Response: Danh sách ảnh đã upload với URL


API 5: Xóa ảnh

DELETE /fields/:id/images/:imageId
Authorization: Bearer {jwt_token}   (Role: FIELD_OWNER)

4.4 FE Integration Guide

Màn hình: /owner/fields/:id/edit

Flow tích hợp:

1. Mount component: GET /fields/:id → prefill form với dữ liệu hiện tại
2. User chỉnh sửa form
3. Submit: PUT /fields/:id với chỉ những trường thay đổi
4. Upload ảnh: gọi POST /fields/:id/images riêng (không kèm trong PUT)
5. Xóa ảnh: DELETE /fields/:id/images/:imageId
6. Cập nhật giờ: PUT /fields/:id/operating-hours (form riêng)

Enums FE cần biết:

enum FieldSize {
  FOOTBALL_5 = 'FOOTBALL_5',
  FOOTBALL_7 = 'FOOTBALL_7',
  FOOTBALL_11 = 'FOOTBALL_11',
}
enum SurfaceType {
  GRASS = 'GRASS',
  ARTIFICIAL_GRASS = 'ARTIFICIAL_GRASS',
  CONCRETE = 'CONCRETE',
  FUTSAL = 'FUTSAL',
}
enum Amenity {
  PARKING = 'PARKING',
  SHOWER = 'SHOWER',
  LIGHTING = 'LIGHTING',
  LOCKER = 'LOCKER',
  CAFE = 'CAFE',
  WIFI = 'WIFI',
}

Lưu ý khi integrate:

  • Error 403 Forbidden: user không phải owner của sân này
  • Ảnh cần dùng FormData API, không phải JSON body
  • Chỉ FIELD_OWNER mới gọi được — kiểm tra role trước khi render route

UC-05: Cập nhật trạng thái sân (Mở/Đóng)

5.1 Tại sao API này tồn tại?

Chủ sân cần khả năng nhanh chóng đóng/mở sân mà không cần vào admin panel — ví dụ đóng sân khi sân bị hỏng, cần bảo trì, hoặc ngày lễ không hoạt động.

5.2 Backend Status

Đã implementPUT /fields/:id/status

5.3 API Specification

PUT /fields/:id/status
Authorization: Bearer {jwt_token}   (Role: FIELD_OWNER)

Request body:

{
  "status": "CLOSED"
}
Status Chủ sân có thể set Admin có thể set
ACTIVE
CLOSED
INACTIVE
PENDING_APPROVAL -

Response (200 OK): Field object với status mới

5.4 FE Integration Guide

Màn hình: Nút toggle trong /owner/fields hoặc /owner/fields/:id

Flow tích hợp:

Trong danh sách sân (UC-03):
    → Mỗi card sân có toggle switch Mở/Đóng
    → User toggle → FE gọi PUT /fields/:id/status
    → Cập nhật UI optimistically
    → Nếu lỗi → revert toggle + hiển thị error

Trạng thái `PENDING_APPROVAL` và `INACTIVE`:
    → Không cho phép toggle (disable switch)
    → Hiển thị badge "Đang chờ duyệt" / "Bị khóa"

UC-06: Xem bảng điều khiển và thống kê doanh thu

6.1 Tại sao API này tồn tại?

Field Owner cần dashboard để theo dõi tình hình kinh doanh: tổng doanh thu, số booking, tỉ lệ lấp đầy theo từng tháng và theo từng sân.

6.2 Backend Status

Đã implementGET /bookings/owner/revenue + GET /bookings/owner/bookings

6.3 API Specification

API 1: Doanh thu tổng hợp

GET /bookings/owner/revenue?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD
Authorization: Bearer {jwt_token}   (Role: FIELD_OWNER)

Gateway tự động fetch danh sách field IDs của owner từ field-service, rồi query booking-service — FE chỉ cần truyền startDate và endDate.

Response mẫu:

{
  "period": { "startDate": "2026-05-01", "endDate": "2026-05-31" },
  "totalRevenue": 15600000,
  "totalBookings": 78,
  "confirmedBookings": 72,
  "cancelledBookings": 6,
  "utilizationRate": 0.73,
  "monthlyBreakdown": [
    {
      "month": "2026-05",
      "revenue": 15600000,
      "bookings": 78
    }
  ],
  "byField": [
    {
      "fieldId": "b2c3d4e5-...",
      "fieldName": "Sân A",
      "revenue": 9600000,
      "bookings": 48
    },
    {
      "fieldId": "c3d4e5f6-...",
      "fieldName": "Sân B",
      "revenue": 6000000,
      "bookings": 30
    }
  ]
}

API 2: Danh sách booking của các sân

GET /bookings/owner/bookings?status=CONFIRMED&page=1&limit=20
Authorization: Bearer {jwt_token}   (Role: FIELD_OWNER)

Query params:

  • status (optional): filter theo status
  • page, limit: phân trang

6.4 FE Integration Guide

Màn hình: /owner/dashboard

Flow tích hợp:

Mount dashboard:
    1. Render date range picker (default: tháng hiện tại)
    2. GET /bookings/owner/revenue?startDate={start}&endDate={end}
       → Render: Total Revenue Card, Total Bookings Card, Cancellation Rate
       → Render: Bar chart doanh thu theo ngày/tuần
       → Render: Pie chart revenue by field
    3. GET /bookings/owner/bookings?status=PENDING
       → Render: "Booking chờ xác nhận" list ở sidebar

Render gợi ý:

  • Revenue card: format VNĐ new Intl.NumberFormat('vi-VN', { style: 'currency', currency: 'VND' }).format(totalRevenue)
  • Utilization rate: hiển thị dạng 73% với progress bar
  • Khi user thay đổi date range → gọi lại API với params mới

Phần 3: Admin Use Cases


UC-07: Quản lý danh sách Người dùng

7.1 Tại sao API này tồn tại?

Admin cần công cụ để quản lý toàn bộ tài khoản người dùng: tìm kiếm, xem thông tin chi tiết, kích hoạt/vô hiệu hóa tài khoản khi có vi phạm, và thay đổi role nếu cần thiết.

7.2 Backend Status

THIẾU trong Gatewayuser-service đã có logic nhưng chưa expose qua gateway cho Admin
✅ user-service: GET /users, GET /users/:id, PATCH /users/:id đã tồn tại
⚠️ Cần thêm: Admin user management controller trong gateway + admin-specific endpoints trong user-service

7.3 Implementation Plan (Backend)

Việc cần làm:

Step 1: Thêm GET /admin/users vào gateway (apps/api-gateway/src/users/)

  • Tạo admin-users.controller.ts trong gateway
  • Proxy tới GET /users của user-service với headers admin auth
  • Thêm query params: search (email/name), role, status, page, limit

Step 2: Thêm GET /admin/users/:id vào gateway

  • Proxy tới GET /users/:id của user-service

Step 3: Thêm PATCH /admin/users/:id/status vào gateway

  • Proxy tới user-service
  • user-service cần có endpoint PATCH /users/:id/status (kiểm tra nếu chưa có)

Step 4: Kiểm tra user-service PATCH /users/:id

  • Endpoint này đã tồn tại nhưng cần đảm bảo hỗ trợ field status trong UpdateUserDto
  • Thêm validation cho phép ADMIN thay đổi status, role

7.4 API Specification (thiết kế mới)

API 1: Danh sách người dùng

GET /admin/users
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Query params:

Param Type Mô tả
page number Trang, default 1
limit number Items/trang, default 20, max 100
search string Tìm kiếm theo email hoặc tên
role string Filter: CUSTOMER \| FIELD_OWNER \| ADMIN
status string Filter: ACTIVE \| BANNED \| SUSPENDED

Response (200 OK):

{
  "data": [
    {
      "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "email": "user@example.com",
      "name": "Nguyễn Văn A",
      "phone": "0901234567",
      "role": "CUSTOMER",
      "status": "ACTIVE",
      "createdAt": "2026-01-15T08:00:00.000Z",
      "updatedAt": "2026-05-20T10:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 350,
    "totalPages": 18
  }
}

API 2: Chi tiết người dùng

GET /admin/users/:id
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Response: User object đầy đủ bao gồm keycloakId, metadata, createdAt


API 3: Cập nhật trạng thái tài khoản

PATCH /admin/users/:id/status
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Request body:

{
  "status": "BANNED",
  "reason": "Vi phạm điều khoản sử dụng — đặt sân ảo nhiều lần"
}
Status Mô tả
ACTIVE Tài khoản bình thường
BANNED Bị cấm vĩnh viễn
SUSPENDED Tạm đình chỉ

Response (200 OK):

{
  "id": "c3d4e5f6-...",
  "email": "user@example.com",
  "status": "BANNED",
  "updatedAt": "2026-05-24T10:00:00.000Z"
}

API 4: Cập nhật role người dùng (optional — dùng khi cần override thủ công)

PATCH /admin/users/:id/role
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Request body:

{ "role": "FIELD_OWNER" }

7.5 FE Integration Guide

Màn hình: /admin/users

Flow tích hợp:

Mount page:
    → GET /admin/users?page=1&limit=20
    → Render table với columns: Avatar, Email, Tên, Role badge, Status badge, Ngày tạo, Actions

Filter/Search:
    → User nhập search → debounce 300ms → GET /admin/users?search={query}
    → User chọn filter role/status → GET /admin/users?role=FIELD_OWNER&status=ACTIVE

Actions trong table:
    → "Xem chi tiết" → GET /admin/users/:id → mở modal/drawer
    → "Cấm tài khoản" → PATCH /admin/users/:id/status {status: "BANNED"}
    → "Kích hoạt lại" → PATCH /admin/users/:id/status {status: "ACTIVE"}

Confirm dialog trước khi BANNED:
    → "Bạn có chắc muốn cấm tài khoản {email}?"
    → Mở textarea nhập lý do (lưu vào reason field)

Role badges:

const roleBadge = {
  ADMIN: { label: 'Admin', color: 'red' },
  FIELD_OWNER: { label: 'Chủ sân', color: 'blue' },
  CUSTOMER: { label: 'Khách hàng', color: 'gray' },
};

UC-08: Quản lý danh sách Chủ sân và Đối tác

8.1 Tại sao API này tồn tại?

Admin cần kiểm soát quy trình onboarding của Field Owner: xem xét đơn đăng ký, duyệt hoặc từ chối. Sau khi duyệt, hệ thống tự động upgrade role của user lên FIELD_OWNER trong cả database lẫn Keycloak. Ngoài ra admin cần xem danh sách các sân đang chờ duyệt và approve/reject sân.

8.2 Backend Status

Đã implement đầy đủ — owner-applications + admin-fields đã có
FE chưa tích hợp

8.3 API Specification

Owner Applications (Đơn đăng ký trở thành chủ sân):

API 1: Danh sách tất cả đơn đăng ký

GET /owner-applications
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Response:

[
  {
    "id": "uuid",
    "userId": "uuid",
    "user": {
      "id": "uuid",
      "email": "owner@example.com",
      "name": "Trần Văn B"
    },
    "status": "PENDING",
    "businessName": "Sân Bóng Thành Công FC",
    "businessAddress": "123 Đường Lê Lợi, Q1",
    "businessPhone": "0901234567",
    "businessLicenseNumber": "GP-2024-001",
    "adminNote": null,
    "createdAt": "2026-05-20T08:00:00.000Z"
  }
]

API 2: Duyệt đơn đăng ký

PATCH /owner-applications/:id/approve
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Hệ thống tự động:

  1. Gán realm role field-owner trong Keycloak
  2. Cập nhật role = FIELD_OWNER trong PostgreSQL
  3. Đánh dấu đơn là APPROVED

Response (200 OK):

{
  "success": true,
  "message": "Đơn đăng ký đã được duyệt. Tài khoản đã được nâng cấp thành Field Owner.",
  "data": { "id": "uuid", "status": "APPROVED" }
}

API 3: Từ chối đơn đăng ký

PATCH /owner-applications/:id/reject
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Request body:

{ "adminNote": "Giấy phép kinh doanh không hợp lệ hoặc đã hết hạn." }

Fields Approval (Sân bóng chờ duyệt):

API 4: Danh sách sân chờ duyệt

GET /admin/fields/pending?page=1&limit=20
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Response: Paginated list của các field với status PENDING_APPROVAL


API 5: Duyệt sân

PUT /admin/fields/:id/approve
Authorization: Bearer {jwt_token}   (Role: ADMIN)

API 6: Từ chối sân

PUT /admin/fields/:id/reject
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Request body:

{ "reason": "Thông tin địa chỉ không chính xác, ảnh chất lượng kém." }

API 7: Danh sách tất cả sân (admin view)

GET /admin/fields?page=1&limit=20&status=ACTIVE
Authorization: Bearer {jwt_token}   (Role: ADMIN)

8.4 FE Integration Guide

Màn hình 1: /admin/owner-applications — Quản lý đơn đăng ký

Mount page:
    → GET /owner-applications
    → Render table: Tên người dùng, Email, Tên doanh nghiệp, Ngày nộp, Status badge, Actions

Filter theo status:
    → Tabs: [Tất cả] [Chờ duyệt] [Đã duyệt] [Từ chối]

Actions:
    → "Xem chi tiết" → mở modal hiển thị businessName, businessAddress, businessPhone, businessLicenseNumber
    → "Duyệt" → confirm dialog → PATCH /owner-applications/:id/approve → refresh list
    → "Từ chối" → mở modal nhập adminNote → PATCH /owner-applications/:id/reject → refresh list

Màn hình 2: /admin/fields — Quản lý sân bóng

Mount page:
    → Tab "Chờ duyệt": GET /admin/fields/pending
    → Tab "Tất cả sân": GET /admin/fields?page=1

Actions trên từng sân:
    → "Duyệt" → PUT /admin/fields/:id/approve
    → "Từ chối" → modal nhập lý do → PUT /admin/fields/:id/reject
    → "Xem chi tiết" → GET /fields/:id

UC-09: Xem thống kê và báo cáo hệ thống

9.1 Tại sao API này tồn tại?

Admin cần visibility toàn diện về sức khỏe hệ thống: tổng số users, tốc độ tăng trưởng, doanh thu trên toàn platform, số sân hoạt động, xu hướng booking. Đây là data để đưa ra quyết định kinh doanh.

9.2 Backend Status

CHƯA CÓ — cần implement mới hoàn toàn

9.3 Implementation Plan (Backend)

Step 1: user-service — thêm stats endpoint (apps/user-service/src/users/)

Tạo GET /users/admin/stats:

  • Truy vấn số users theo role (CUSTOMER, FIELD_OWNER, ADMIN)
  • Truy vấn số users theo status (ACTIVE, BANNED, SUSPENDED)
  • Truy vấn số users đăng ký mới trong 30 ngày qua (group by day)

Step 2: field-service — thêm stats endpoint (apps/field-service/src/fields/)

Tạo GET /fields/admin/stats:

  • Tổng số fields theo status (ACTIVE, PENDING_APPROVAL, CLOSED, REJECTED)
  • Số fields mới tạo trong 30 ngày qua

Step 3: booking-service — thêm admin stats endpoint (apps/booking-service/src/bookings/)

Tạo GET /bookings/admin/stats?startDate=&endDate=:

  • Tổng số bookings trong period, breakdown theo status
  • Tổng doanh thu trong period
  • Booking trends: group by ngày

Step 4: gateway — thêm admin stats aggregator (apps/api-gateway/src/)

Tạo GET /admin/stats:

  • Gọi đồng thời (Promise.all) tới cả 3 services trên
  • Aggregate kết quả thành 1 response duy nhất cho FE

9.4 API Specification (thiết kế mới)

API: System overview stats

GET /admin/stats?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Query params:

  • startDate (optional): default là 30 ngày trước
  • endDate (optional): default là hôm nay

Response (200 OK):

{
  "generatedAt": "2026-05-24T10:00:00.000Z",
  "period": {
    "startDate": "2026-04-24",
    "endDate": "2026-05-24"
  },
  "users": {
    "total": 1250,
    "byRole": {
      "CUSTOMER": 1180,
      "FIELD_OWNER": 68,
      "ADMIN": 2
    },
    "byStatus": {
      "ACTIVE": 1230,
      "BANNED": 15,
      "SUSPENDED": 5
    },
    "newInPeriod": 87,
    "growthTrend": [
      { "date": "2026-04-24", "count": 3 },
      { "date": "2026-04-25", "count": 5 }
    ]
  },
  "fields": {
    "total": 142,
    "byStatus": {
      "ACTIVE": 115,
      "PENDING_APPROVAL": 12,
      "CLOSED": 8,
      "REJECTED": 7
    },
    "newInPeriod": 14
  },
  "bookings": {
    "totalInPeriod": 2340,
    "byStatus": {
      "CONFIRMED": 2100,
      "CANCELLED": 180,
      "PENDING": 60
    },
    "totalRevenue": 840000000,
    "cancellationRate": 0.077,
    "bookingTrend": [
      { "date": "2026-04-24", "count": 72, "revenue": 28800000 },
      { "date": "2026-04-25", "count": 85, "revenue": 34000000 }
    ]
  }
}

9.5 FE Integration Guide

Màn hình: /admin/stats

Flow tích hợp:

Mount page:
    → Render date range picker (default: 30 ngày)
    → GET /admin/stats?startDate={start}&endDate={end}

Layout gợi ý:
    Row 1: 4 KPI cards
        [Tổng Users: 1,250] [Sân Hoạt Động: 115] [Booking tháng: 2,340] [Doanh thu: 840M]

    Row 2:
        Left (60%): Line chart "Booking trend" (bookings.bookingTrend by date)
        Right (40%): Pie chart "Field status" (fields.byStatus)

    Row 3:
        Left (50%): Bar chart "User growth" (users.growthTrend by date)
        Right (50%): Donut chart "User roles" (users.byRole)

    Row 4: Summary cards
        [New Users: +87] [New Fields: +14] [Cancellation Rate: 7.7%]

UC-10: Tổng quan Admin Dashboard

10.1 Tại sao API này tồn tại?

Trang đầu tiên admin thấy khi đăng nhập cần cung cấp snapshot nhanh về tình hình hệ thống: những việc cần xử lý ngay (đơn pending, sân chờ duyệt) và các metrics quan trọng hôm nay. Mục tiêu là giúp admin không bỏ sót việc quan trọng.

10.2 Backend Status

CHƯA CÓ — cần implement mới

10.3 Implementation Plan (Backend)

Option A (Recommended): Gateway aggregation
Gateway gọi song song nhiều endpoints và aggregate:

  • GET /owner-applications → đếm PENDING applications
  • GET /admin/fields/pending → đếm pending fields
  • GET /admin/stats (UC-09) → lấy today's metrics

Option B: Dedicated endpoint
Tạo GET /admin/dashboard ở booking-service (service có nhiều data nhất) rồi proxy qua gateway.

Recommendation: Option A đơn giản hơn, tái sử dụng code đã có, và gateway đã có pattern này (xem GET /bookings/owner/revenue).

10.4 API Specification (thiết kế mới)

GET /admin/dashboard
Authorization: Bearer {jwt_token}   (Role: ADMIN)

Response (200 OK):

{
  "generatedAt": "2026-05-24T10:30:00.000Z",
  "pendingActions": {
    "ownerApplications": 5,
    "fieldsAwaitingApproval": 12,
    "totalActionRequired": 17
  },
  "today": {
    "date": "2026-05-24",
    "newUsers": 8,
    "newBookings": 42,
    "revenue": 16800000,
    "newFields": 2
  },
  "thisMonth": {
    "totalBookings": 2340,
    "totalRevenue": 840000000,
    "newUsers": 87,
    "cancellationRate": 0.077
  },
  "systemHealth": {
    "totalActiveUsers": 1230,
    "totalActiveFields": 115,
    "totalConfirmedBookings": 2100
  }
}

10.5 FE Integration Guide

Màn hình: /admin/dashboard (trang landing sau login)

Flow tích hợp:

Mount dashboard:
    → GET /admin/dashboard

Layout gợi ý:
    Header: "Xin chào, Admin! Hôm nay [ngày]"

    Alert banner (nếu pendingActions.totalActionRequired > 0):
        "⚠️ Có 17 việc cần xử lý:
         - 5 đơn đăng ký chủ sân chờ duyệt → [Xem đơn]
         - 12 sân bóng chờ phê duyệt → [Xem sân]"

    Row: Today's metrics (4 cards)
        [+8 User mới] [42 Booking] [16.8M Doanh thu] [+2 Sân mới]

    Row: This month summary (3 cards)
        [2,340 Booking] [840M Doanh thu] [7.7% Hủy đặt]

    Row: System Health (3 cards)
        [1,230 Users Active] [115 Sân Active] [2,100 Booking Confirmed]

    Quick links:
        [Quản lý đơn đăng ký] [Duyệt sân] [Xem báo cáo] [Quản lý Users]

Lưu ý khi integrate:

  • Gọi GET /admin/dashboard mỗi khi mount component (không cache lâu)
  • Refresh tự động mỗi 5 phút nếu admin đang ở trang dashboard
  • pendingActions.totalActionRequired > 0 → badge đỏ trên sidebar nav

Phần 4: Tổng kết Implementation Checklist

Backend Tasks (theo thứ tự ưu tiên)

Priority 1 — Admin User Management (Gateway proxy)

  • [ ] [api-gateway] Tạo AdminUsersController trong apps/api-gateway/src/users/admin-users.controller.ts
  • [ ] GET /admin/users — proxy tới user-service /users với search/filter
  • [ ] GET /admin/users/:id — proxy tới user-service /users/:id
  • [ ] PATCH /admin/users/:id/status — proxy tới user-service
  • [ ] Thêm vào app.module.ts
  • [ ] [user-service] Kiểm tra và đảm bảo PATCH /users/:id hỗ trợ field status
  • [ ] Thêm PATCH /users/:id/status endpoint riêng nếu cần
  • [ ] Thêm search query param vào GET /users (findAll)

Priority 2 — Admin System Stats

  • [ ] [user-service] Thêm GET /users/admin/stats endpoint
  • [ ] Count by role, count by status, new users by date
  • [ ] [field-service] Thêm GET /fields/admin/stats endpoint
  • [ ] Count by status, new fields by date
  • [ ] [booking-service] Thêm GET /bookings/admin/stats endpoint
  • [ ] Total bookings/revenue in period, trend by date
  • [ ] [api-gateway] Tạo AdminStatsController
  • [ ] GET /admin/stats — aggregate gọi 3 services song song (Promise.all)

Priority 3 — Admin Dashboard Overview

  • [ ] [api-gateway] Thêm GET /admin/dashboard vào AdminStatsController
  • [ ] Aggregate: pending owner apps count + pending fields count + today stats
  • [ ] Dùng Promise.all để gọi song song

Frontend Tasks

Customer

  • [ ] Tích hợp schedule grid vào trang detail sân
  • [ ] Build booking flow (chọn slot → tạo booking → thanh toán)
  • [ ] Build trang "Lịch sử đặt sân" với filter/pagination

Field Owner

  • [ ] Build owner dashboard với danh sách sân
  • [ ] Build form edit sân với upload ảnh
  • [ ] Thêm toggle Mở/Đóng sân trên dashboard
  • [ ] Build revenue dashboard với charts

Admin

  • [ ] Build admin layout với sidebar navigation
  • [ ] Build user management table với search/filter
  • [ ] Build owner applications management (approve/reject flow)
  • [ ] Build field approval management
  • [ ] Build system stats page với charts
  • [ ] Build admin overview dashboard

Phần 5: Authentication Reference

Tất cả các API (trừ public) đều yêu cầu JWT token trong header:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Token được lấy từ Keycloak sau khi đăng nhập. Role của user được embed trong JWT claims.

Role-based access summary:

Endpoint prefix Required Role
GET /fields, GET /bookings/fields/... Public
POST /bookings, GET /bookings/me Any authenticated user
GET /fields/owner/..., PUT /fields/:id, GET /bookings/owner/... FIELD_OWNER
GET /admin/..., PUT /admin/..., PATCH /owner-applications/... ADMIN

FE handling JWT:

  • Lưu token vào httpOnly cookie hoặc memory (không dùng localStorage do XSS risk)
  • Tự động refresh token trước khi hết hạn (dùng Keycloak JS adapter hoặc tự implement refresh flow)
  • Khi nhận 401 Unauthorized → redirect về login page
  • Khi nhận 403 Forbidden → hiển thị "Bạn không có quyền truy cập" + redirect

Phần 6: Error Handling Reference

Tất cả API trả về error theo format chuẩn:

{
  "statusCode": 409,
  "message": "Time slot not available",
  "error": "Conflict"
}
HTTP Status Ý nghĩa FE nên làm gì
400 Bad Request Input không hợp lệ Hiển thị validation error inline
401 Unauthorized Chưa đăng nhập hoặc token hết hạn Redirect về login
403 Forbidden Không đủ quyền Toast error + redirect
404 Not Found Không tìm thấy resource Hiển thị empty state hoặc 404 page
409 Conflict Conflict (slot đã bị đặt, etc.) Toast warning + refresh data
503 Service Unavailable Service đang chết Toast error "Hệ thống đang bảo trì, vui lòng thử lại"