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ó và 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/endTimetrong 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-gridcho calendar view, dùngavailabilitycho 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): default1limit(optional): default20status(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
expiresAttrong 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
fieldNametrongGET /bookings/medo 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
✅ Đã implement —
GET /fields/owner/my-fieldstrong 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 countGET /bookings/owner/revenue?startDate=...&endDate=...cho doanh thu
💡 Enhancement opportunity: Backend team có thể enhance
GET /fields/owner/my-fieldsđể trả thêmbookingCountTodayvàmonthlyRevenuenế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
FormDataAPI, 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
✅ Đã implement —
PUT /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
✅ Đã implement —
GET /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 statuspage,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 Gateway —
user-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.tstrong gateway - Proxy tới
GET /userscủ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/:idcủ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
statustrongUpdateUserDto - 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:
- Gán realm role
field-ownertrong Keycloak - Cập nhật
role = FIELD_OWNERtrong PostgreSQL - Đá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ướcendDate(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 applicationsGET /admin/fields/pending→ đếm pending fieldsGET /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/dashboardmỗ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
AdminUsersControllertrongapps/api-gateway/src/users/admin-users.controller.ts - [ ]
GET /admin/users— proxy tới user-service/usersvớ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/:idhỗ trợ fieldstatus - [ ] Thêm
PATCH /users/:id/statusendpoint riêng nếu cần - [ ] Thêm
searchquery param vàoGET /users(findAll)
Priority 2 — Admin System Stats
- [ ] [user-service] Thêm
GET /users/admin/statsendpoint - [ ] Count by role, count by status, new users by date
- [ ] [field-service] Thêm
GET /fields/admin/statsendpoint - [ ] Count by status, new fields by date
- [ ] [booking-service] Thêm
GET /bookings/admin/statsendpoint - [ ] 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/dashboardvàoAdminStatsController - [ ] 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
httpOnlycookie hoặc memory (không dùnglocalStoragedo 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" |