So Sánh Thay Đổi Gói Đăng Ký
Mô Tả Tổng Quan
Tính năng So Sánh Thay Đổi Gói Đăng Ký cho phép chủ nhóm xem trước tác động của việc chuyển sang gói đăng ký mới trước khi xác nhận thay đổi. Khi người dùng lên lịch thay đổi gói (nâng cấp hoặc hạ cấp), họ cần hiểu rõ các giới hạn tài nguyên của gói mới sẽ ảnh hưởng đến thành viên nhóm và danh sách mong muốn hiện có như thế nào.
Tính năng này cung cấp so sánh chi tiết cho thấy:
- Giới hạn tài nguyên của gói hiện tại vs gói mục tiêu
- Thành viên vượt quá giới hạn thành viên mới (khi hạ cấp)
- Danh sách mong muốn vi phạm ràng buộc tài nguyên
- Danh sách mong muốn vượt quá giới hạn nhóm sản phẩm mới
Quy trình yêu cầu chủ nhóm xem xét các tác động tiềm năng và đưa ra quyết định sáng suốt về việc vô hiệu hóa thành viên nào và đặt danh sách mong muốn nào sang trạng thái huấn luyện thủ công trước khi có thể xác nhận thay đổi gói.
Điều kiện tiên quyết:
- Người dùng phải được xác thực
- Người dùng phải là người tạo nhóm (chủ sở hữu)
- Nhóm phải có đăng ký đang hoạt động
- Phải có thay đổi gói đang chờ xử lý (thay đổi gói đã lên lịch)
API: API So Sánh Thay Đổi Đăng Ký
Sơ Đồ Hoạt Động
---
config:
theme: base
layout: dagre
flowchart:
curve: linear
htmlLabels: true
themeVariables:
edgeLabelBackground: "transparent"
---
flowchart TB
%% Main components
SubscriptionController[SubscriptionController]
SubscriptionService(SubscriptionService)
UserService(UserService)
WishlistToGroupService(WishlistToGroupService)
SubscriptionModel[[Subscription]]
SubscriptionHistoryModel[[SubscriptionHistory]]
GroupModel[[Group]]
GroupMemberModel[[GroupMember]]
WishlistToGroupModel[[WishlistToGroup]]
SubDB[(subscriptions)]
SubHistDB[(subscription_histories)]
GroupDB[(groups)]
GroupMemberDB[(group_members)]
WishlistDB[(wishlist_to_groups)]
subgraph Controllers
GetCompareChangeController["GET /subscription/compare-change"]
ConfirmChangeController["POST /subscription/confirm-change"]
end
subgraph Services
SubscriptionService
UserService
WishlistToGroupService
end
subgraph Models
SubscriptionModel
SubscriptionHistoryModel
GroupModel
GroupMemberModel
WishlistToGroupModel
end
subgraph Databases
SubDB
SubHistDB
GroupDB
GroupMemberDB
WishlistDB
end
%% GET compare-change flow
GetCompareChangeController --- Step1A[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>1A</span>
<p style='margin-top: 8px'>Xác Thực Quyền Người Dùng</p>
</div>
]
Step1A --> GetCompareChangeController
GetCompareChangeController --- Step2A[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>2A</span>
<p style='margin-top: 8px'>Lấy Đăng Ký Đang Hoạt Động</p>
</div>
]
Step2A --> SubscriptionModel
GetCompareChangeController --- Step3A[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3A</span>
<p style='margin-top: 8px'>Lấy Bản Xem Trước So Sánh Gói</p>
</div>
]
Step3A --> SubscriptionService
GetCompareChangeController --- Step4A[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>4A</span>
<p style='margin-top: 8px'>Tính Sự Khác Biệt Thành Viên</p>
</div>
]
Step4A --> GroupMemberModel
GetCompareChangeController --- Step5A[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>5A</span>
<p style='margin-top: 8px'>Tính Sự Khác Biệt Danh Sách Mong Muốn</p>
</div>
]
Step5A --> WishlistToGroupModel
GetCompareChangeController --- Step6A[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>6A</span>
<p style='margin-top: 8px'>Trả Về Phản Hồi Xem Trước</p>
</div>
]
Step6A --> GetCompareChangeController
%% POST confirm-change flow
ConfirmChangeController --- Step1B[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>1B</span>
<p style='margin-top: 8px'>Xác Thực Quyền Người Dùng</p>
</div>
]
Step1B --> ConfirmChangeController
ConfirmChangeController --- Step2B[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>2B</span>
<p style='margin-top: 8px'>Xác Thực Dữ Liệu Yêu Cầu</p>
</div>
]
Step2B --> ConfirmChangeController
ConfirmChangeController --- Step3B[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3B</span>
<p style='margin-top: 8px'>Bắt Đầu Transaction</p>
</div>
]
Step3B --> SubscriptionService
ConfirmChangeController --- Step4B[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>4B</span>
<p style='margin-top: 8px'>Vô Hiệu Hóa Thành Viên</p>
</div>
]
Step4B --> UserService
ConfirmChangeController --- Step5B[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>5B</span>
<p style='margin-top: 8px'>Đặt Danh Sách Mong Muốn Sang Thủ Công</p>
</div>
]
Step5B --> WishlistToGroupService
ConfirmChangeController --- Step6B[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>6B</span>
<p style='margin-top: 8px'>Commit Transaction</p>
</div>
]
Step6B --> SubscriptionService
%% Service to Model relationships
SubscriptionService -.-> SubscriptionModel
SubscriptionService -.-> SubscriptionHistoryModel
SubscriptionService -.-> GroupModel
UserService -.-> GroupMemberModel
WishlistToGroupService -.-> WishlistToGroupModel
%% Model to Database relationships
SubscriptionModel -.-> SubDB
SubscriptionHistoryModel -.-> SubHistDB
GroupModel -.-> GroupDB
GroupMemberModel -.-> GroupMemberDB
WishlistToGroupModel -.-> WishlistDB
%% Styling
style SubDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style SubHistDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style GroupDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style GroupMemberDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style WishlistDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style Controllers fill:#e6f3ff
style Services fill:#f0f8e6
style Models fill:#fff0f5
style Databases fill:#ffe6cc
style Step1A fill:transparent,stroke:transparent,stroke-width:1px
style Step2A fill:transparent,stroke:transparent,stroke-width:1px
style Step3A fill:transparent,stroke:transparent,stroke-width:1px
style Step4A fill:transparent,stroke:transparent,stroke-width:1px
style Step5A fill:transparent,stroke:transparent,stroke-width:1px
style Step6A fill:transparent,stroke:transparent,stroke-width:1px
style Step1B fill:transparent,stroke:transparent,stroke-width:1px
style Step2B fill:transparent,stroke:transparent,stroke-width:1px
style Step3B fill:transparent,stroke:transparent,stroke-width:1px
style Step4B fill:transparent,stroke:transparent,stroke-width:1px
style Step5B fill:transparent,stroke:transparent,stroke-width:1px
style Step6B fill:transparent,stroke:transparent,stroke-width:1px
Case 1: Lấy Bản Xem Trước So Sánh Thay Đổi - Thành Công
Mô Tả
Chủ nhóm thành công lấy bản xem trước các thay đổi giữa gói đăng ký hiện tại và gói mục tiêu. Hệ thống phân tích giới hạn thành viên và ràng buộc danh sách mong muốn để xác định các tác động tiềm năng.
Sơ Đồ Trình Tự
sequenceDiagram
participant Client
participant Controller as SubscriptionController
participant Service as SubscriptionService
participant SubModel as Subscription
participant HistModel as SubscriptionHistory
participant GroupModel as Group
participant MemberModel as GroupMember
participant WishlistModel as WishlistToGroup
participant Database
Note over Client,Database: GET /subscription/compare-change
rect rgb(200, 255, 200)
Note right of Client: Luồng Trường Hợp Thành Công
Client->>Controller: GET /subscription/compare-change
rect rgb(255, 255, 200)
Note right of Controller: Kiểm Tra Quyền
Controller->>Controller: Kiểm tra người dùng là chủ nhóm
end
rect rgb(200, 230, 255)
Note right of Controller: Lấy Dữ Liệu Đăng Ký
Controller->>SubModel: Lấy đăng ký đang hoạt động
SubModel->>Database: SELECT * FROM subscriptions WHERE group_id AND status = 'active'
Database-->>SubModel: Trả về đăng ký đang hoạt động
SubModel-->>Controller: Trả về đối tượng đăng ký
Controller->>HistModel: Lấy gói hiện tại (đã thanh toán mới nhất)
HistModel->>Database: SELECT * FROM subscription_histories WHERE payment_status = 'paid' ORDER BY paid_at DESC
Database-->>HistModel: Trả về gói hiện tại
Controller->>HistModel: Lấy gói mục tiêu (thay đổi đang chờ mới nhất)
HistModel->>Database: SELECT * FROM subscription_histories WHERE type = 'change' AND payment_status = 'pending'
Database-->>HistModel: Trả về gói mục tiêu
end
rect rgb(200, 255, 255)
Note right of Controller: Tính Sự Khác Biệt Thành Viên
Controller->>Service: calculateMemberDifferences(group, currentPlan, targetPlan)
Service->>MemberModel: Lấy số lượng thành viên hiện tại
MemberModel->>Database: SELECT COUNT(*) FROM group_members WHERE group_id
Database-->>MemberModel: Trả về số lượng
Service->>Service: So sánh với giới hạn max_member của gói mục tiêu
Service->>MemberModel: Lấy thành viên không phải người tạo nếu vượt giới hạn
MemberModel->>Database: SELECT * FROM group_members WHERE group_id AND is_creator = false
Database-->>MemberModel: Trả về danh sách thành viên
Service-->>Controller: Trả về sự khác biệt thành viên
end
rect rgb(230, 200, 255)
Note right of Controller: Tính Sự Khác Biệt Danh Sách Mong Muốn
Controller->>Service: calculateWishlistDifferences(group, currentPlan, targetPlan)
Service->>WishlistModel: Lấy tất cả danh sách mong muốn với dữ liệu liên quan
WishlistModel->>Database: SELECT * FROM wishlist_to_groups WHERE group_id
Database-->>WishlistModel: Trả về danh sách mong muốn
Service->>Service: Kiểm tra từng danh sách mong muốn với giới hạn mục tiêu
Service->>Service: Xác định vô hiệu hóa bắt buộc (vi phạm giới hạn tài nguyên)
Service->>Service: Xác định vô hiệu hóa tùy chọn (vượt max_product_group)
Service-->>Controller: Trả về sự khác biệt danh sách mong muốn
end
Controller-->>Client: 200 OK (dữ liệu so sánh)
end
rect rgb(255, 200, 200)
Note right of Client: Các Kịch Bản Lỗi
rect rgb(255, 230, 230)
alt Không Phải Chủ Nhóm
Controller-->>Client: 403 Forbidden
else Không Có Đăng Ký Đang Hoạt Động
SubModel-->>Controller: Không tìm thấy đăng ký đang hoạt động
Controller-->>Client: 400 Bad Request
else Không Có Gói Mục Tiêu
HistModel-->>Controller: Không tìm thấy thay đổi đang chờ
Controller-->>Client: 400 Bad Request
end
end
end
Các Bước
Bước 1: Kiểm Tra Quyền
- Mô tả: Xác minh người dùng thực hiện yêu cầu là chủ nhóm
- Xác thực:
- Người dùng phải được xác thực (Bearer token)
groupMember.is_creatorcủa người dùng phải làtrue
- Điều kiện lỗi: Nếu không phải chủ sở hữu, trả về 403 Forbidden với thông báo "アクセスが拒否されました。"
Bước 2: Lấy Đăng Ký Đang Hoạt Động
- Mô tả: Lấy đăng ký đang hoạt động hiện tại của nhóm
- Hành động: Truy vấn bảng
subscriptionscho đăng ký vớistatus = 'active'vàgroup_idkhớp - Điều kiện lỗi: Nếu không có đăng ký đang hoạt động, trả về 400 với thông báo "アクティブなサブスクリプションがありません。"
Bước 3: Lấy Gói Hiện Tại và Gói Mục Tiêu
- Mô tả: Lấy các bản ghi lịch sử đăng ký để so sánh
- Gói hiện tại: Bản ghi
subscription_historymới nhất vớipayment_status = 'paid'(sắp xếp theopaid_at DESC) - Gói mục tiêu: Bản ghi
subscription_historymới nhất vớitype = 'change'vàpayment_status = 'pending' - Điều kiện lỗi: Nếu không có gói mục tiêu, trả về 400 với thông báo "変更予定のプランがありません。"
Bước 4: Tính Sự Khác Biệt Thành Viên
- Mô tả: Phân tích giới hạn thành viên của gói mục tiêu ảnh hưởng đến thành viên hiện tại như thế nào
- Hành động:
- Lấy số lượng thành viên hiện tại từ bảng
group_members - So sánh với
target_plan.max_member - Nếu vượt giới hạn, tính
excess_member_count = current_count - max_member - Lấy danh sách thành viên không phải người tạo (
is_creator = false) có thể vô hiệu hóa
- Lấy số lượng thành viên hiện tại từ bảng
- Các trường phản hồi:
is_over_limit: booleancurrent_member_count: integercurrent_member_limit: từ gói hiện tạinew_member_limit: từ gói mục tiêuexcess_member_count: integermembers_to_choose: mảng các đối tượng thành viên (user_id, name, role)
Bước 5: Tính Sự Khác Biệt Danh Sách Mong Muốn
- Mô tả: Phân tích giới hạn tài nguyên của gói mục tiêu ảnh hưởng đến danh sách mong muốn như thế nào
- Hành động:
- Lấy tất cả danh sách mong muốn cho nhóm với dữ liệu liên quan:
wishlistProductswishlistCategorieswishlistSearchQuerieswishlistCategoryViewpointReference(với dữ liệu viewpoint lồng nhau)
- Đối với mỗi danh sách mong muốn, đếm tài nguyên:
- Số lượng sản phẩm
- Số lượng danh mục
- Số lượng truy vấn tìm kiếm
- Số lượng điểm nhìn
- Phân loại danh sách mong muốn:
- Vô hiệu hóa bắt buộc: Vi phạm BẤT KỲ giới hạn tài nguyên nào
products > max_productcategories > max_categorysearch_queries > max_search_queryviewpoints > max_viewpoint
- Vô hiệu hóa tùy chọn: Danh sách mong muốn hợp lệ nhưng
total_valid_wishlist > max_product_group
- Vô hiệu hóa bắt buộc: Vi phạm BẤT KỲ giới hạn tài nguyên nào
- Lấy tất cả danh sách mong muốn cho nhóm với dữ liệu liên quan:
- Các trường phản hồi:
is_over_limit: booleantotal_wishlist: tổng sốtotal_valid_wishlist: số lượng danh sách mong muốn đáp ứng yêu cầu tài nguyêntotal_excess: danh sách mong muốn hợp lệ vượt quáforce_deactivation: mảng với lý dooptional_deactivation: mảng danh sách mong muốn hợp lệ
Bước 6: Trả Về Phản Hồi So Sánh
- Mô tả: Định dạng và trả về dữ liệu so sánh đầy đủ
- Cấu trúc phản hồi:
{ "status": true, "message": "プラン変更のプレビューを取得しました。", "data": { "current_plan": { "slug": "plan_slug", "name": "Tên Gói", "limits": { "max_member": 5, "max_product_group": 10, "max_product": 50, "max_category": 20, "max_search_query": 100, "max_viewpoint": 10 } }, "target_plan": { /* cùng cấu trúc */ }, "differences": { "members": { /* dữ liệu sự khác biệt thành viên */ }, "wishlists": { /* dữ liệu sự khác biệt danh sách mong muốn */ } } } }
Các Bảng và Trường Cơ Sở Dữ Liệu Liên Quan
erDiagram
subscriptions {
bigint id PK
bigint package_id FK
bigint package_plan_id FK
bigint group_id FK "Tham chiếu đến bảng groups"
bigint user_id FK
string status "Active, Canceled, Pending Cancellation, Past Due, Unpaid"
timestamp created_at
timestamp updated_at
}
subscription_histories {
bigint id PK
bigint subscription_id FK "Tham chiếu đến bảng subscriptions"
bigint package_id FK
bigint package_plan_id FK
bigint group_id FK
bigint user_id FK
string old_plan_id "Dữ liệu gói cũ khi gói được thay đổi"
string type "Loại đăng ký"
integer max_member
integer max_product_group
integer max_product
integer max_category
integer max_search_query
integer max_viewpoint
string payment_status
tinyint status "0:Inactive, 1: Active"
timestamp paid_at
timestamp created_at
}
groups {
bigint id PK
string name
bigint created_by FK "Tham chiếu đến bảng users"
integer status "0: Inactive, 1: Active"
timestamp created_at
}
group_members {
bigint id PK
bigint group_id FK "Tham chiếu đến bảng groups"
bigint user_id FK "Tham chiếu đến bảng users"
bigint group_role_id FK
boolean is_creator "Cờ cho biết thành viên có là người tạo nhóm không"
timestamp joined_at
timestamp created_at
}
wishlist_to_groups {
bigint id PK
bigint group_id FK "Tham chiếu đến bảng groups"
bigint created_by FK "Tham chiếu đến bảng users"
bigint subscription_id FK "Tham chiếu đến bảng subscriptions"
string name "Tên của danh sách mong muốn"
string slug "Slug của danh sách mong muốn"
integer status "0: Inactive, 1: Active, 3: Canceled"
timestamp created_at
}
subscriptions ||--o{ subscription_histories : has
groups ||--|| subscriptions : has
groups ||--o{ group_members : has
groups ||--o{ wishlist_to_groups : has
Xử Lý Lỗi
Ghi log:
- Tất cả lỗi được ghi vào log ứng dụng với
logThrow($th) - Lỗi nghiêm trọng có thể kích hoạt thông báo Slack
Chi Tiết Lỗi:
| Mã Trạng Thái | Thông Báo Lỗi | Mô Tả |
|---|---|---|
| 401 | "未認証です。" | Người dùng không được xác thực hoặc token không hợp lệ |
| 403 | "アクセスが拒否されました。" | Người dùng không phải là chủ nhóm |
| 400 | "アクティブなサブスクリプションがありません。" | Không tìm thấy đăng ký đang hoạt động cho nhóm |
| 400 | "変更予定のプランがありません。" | Không tìm thấy thay đổi gói đang chờ xử lý |
| 400 | "プラン変更のプレビューに失敗しました。" | Lỗi chung trong quá trình tạo bản xem trước |
Case 2: Xác Nhận So Sánh Thay Đổi - Thành Công
Mô Tả
Chủ nhóm xác nhận thay đổi gói và áp dụng các vô hiệu hóa thành viên và thay đổi trạng thái danh sách mong muốn được chỉ định. Tất cả các thao tác được bọc trong một transaction cơ sở dữ liệu để đảm bảo tính nhất quán của dữ liệu.
Sơ Đồ Trình Tự
sequenceDiagram
participant Client
participant Controller as SubscriptionController
participant Request as ConfirmCompareChangeRequest
participant Service as SubscriptionService
participant UserService as UserService
participant WishlistService as WishlistToGroupService
participant SubModel as Subscription
participant HistModel as SubscriptionHistory
participant GroupMemberModel as GroupMember
participant WishlistModel as WishlistToGroup
participant Database
Note over Client,Database: POST /subscription/confirm-change
rect rgb(200, 255, 200)
Note right of Client: Luồng Trường Hợp Thành Công
Client->>Controller: POST /subscription/confirm-change (members_to_inactive, wishlists_to_manual)
rect rgb(255, 255, 200)
Note right of Controller: Kiểm Tra Quyền
Controller->>Controller: Kiểm tra người dùng là chủ nhóm
end
rect rgb(200, 230, 255)
Note right of Controller: Xác Thực Yêu Cầu
Controller->>Request: validate(data)
Request->>Request: Xác thực định dạng members_to_inactive
Request->>GroupMemberModel: Kiểm tra ID thành viên thuộc nhóm
GroupMemberModel->>Database: SELECT user_id FROM group_members WHERE group_id AND user_id IN (...)
Database-->>GroupMemberModel: Trả về thành viên khớp
Request->>Request: Xác thực định dạng wishlists_to_manual
Request->>WishlistModel: Kiểm tra slug danh sách mong muốn thuộc nhóm
WishlistModel->>Database: SELECT slug FROM wishlist_to_groups WHERE group_id AND slug IN (...)
Database-->>WishlistModel: Trả về danh sách mong muốn khớp
Request-->>Controller: Xác thực thành công
end
rect rgb(200, 255, 255)
Note right of Controller: Xác Minh Trạng Thái Đăng Ký
Controller->>SubModel: Lấy đăng ký đang hoạt động
SubModel->>Database: SELECT * FROM subscriptions WHERE group_id AND status = 'active'
Database-->>SubModel: Trả về đăng ký đang hoạt động
Controller->>HistModel: Lấy gói hiện tại (đã thanh toán mới nhất)
HistModel->>Database: SELECT * FROM subscription_histories WHERE subscription_id AND payment_status = 'paid'
Database-->>HistModel: Trả về gói hiện tại
Controller->>HistModel: Lấy gói mục tiêu (thay đổi đang chờ mới nhất)
HistModel->>Database: SELECT * FROM subscription_histories WHERE subscription_id AND type = 'change'
Database-->>HistModel: Trả về gói mục tiêu
end
rect rgb(230, 200, 255)
Note right of Controller: Thực Thi Thay Đổi với Transaction
Controller->>Service: executeCompareChange(members, wishlists)
Service->>Database: BEGIN TRANSACTION
alt Thành Viên Cần Vô Hiệu Hóa
Service->>UserService: updateStatusByIds(memberIds, 'inactive')
UserService->>GroupMemberModel: Cập nhật trạng thái người dùng
GroupMemberModel->>Database: UPDATE users SET status = 'inactive' WHERE id IN (...)
Database-->>GroupMemberModel: Xác nhận cập nhật
end
alt Danh Sách Mong Muốn Sang Thủ Công
Service->>WishlistService: changeTranningStatusBySlugs(wishlistSlugs, 'manual')
WishlistService->>WishlistModel: Cập nhật trạng thái huấn luyện
WishlistModel->>Database: UPDATE wishlist_to_groups SET training_status = 'manual' WHERE slug IN (...)
Database-->>WishlistModel: Xác nhận cập nhật
end
Service->>Database: COMMIT TRANSACTION
Service-->>Controller: Trả về kết quả thành công
end
Controller-->>Client: 200 OK (thông báo thành công)
end
rect rgb(255, 200, 200)
Note right of Client: Các Kịch Bản Lỗi
rect rgb(255, 230, 230)
alt Không Phải Chủ Nhóm
Controller-->>Client: 403 Forbidden
else Xác Thực Thất Bại
Request-->>Controller: Lỗi xác thực
Controller-->>Client: 400 Bad Request (lỗi xác thực)
else Không Có Đăng Ký Đang Hoạt Động
SubModel-->>Controller: Không tìm thấy đăng ký
Controller-->>Client: 400 Bad Request
else Không Có Gói Mục Tiêu
HistModel-->>Controller: Không có thay đổi đang chờ
Controller-->>Client: 400 Bad Request
else Lỗi Transaction
Database-->>Service: Lỗi cơ sở dữ liệu
Service->>Database: ROLLBACK TRANSACTION
Service-->>Controller: Kết quả lỗi
Controller-->>Client: 400 Bad Request
end
end
end
Các Bước
Bước 1: Kiểm Tra Quyền
- Mô tả: Xác minh người dùng thực hiện yêu cầu là chủ nhóm
- Giống Case 1, Bước 1
Bước 2: Xác Thực Yêu Cầu
- Mô tả: Xác thực các tham số trong body yêu cầu
- Quy tắc xác thực:
members_to_inactive(tùy chọn, chuỗi):- Phải là các ID người dùng phân tách bằng dấu phẩy
- Tất cả ID người dùng phải thuộc nhóm hiện tại
- Không thể bao gồm người tạo nhóm
wishlists_to_manual(tùy chọn, chuỗi):- Phải là các slug danh sách mong muốn phân tách bằng dấu phẩy
- Tất cả slug phải thuộc nhóm hiện tại
- Truy vấn xác thực:
- Cho thành viên:
SELECT user_id FROM group_members WHERE group_id = ? AND user_id IN (?) - Cho danh sách mong muốn:
SELECT slug FROM wishlist_to_groups WHERE group_id = ? AND slug IN (?)
- Cho thành viên:
- Điều kiện lỗi:
- ID thành viên không hợp lệ: Trả về 400 với thông báo "Các thành viên sau không thuộc nhóm của bạn: {ids}"
- Slug danh sách mong muốn không hợp lệ: Trả về 400 với thông báo "Các danh sách mong muốn sau không thuộc nhóm của bạn: {slugs}"
Bước 3: Xác Minh Trạng Thái Đăng Ký
- Mô tả: Đảm bảo đăng ký và gói mục tiêu tồn tại
- Xác thực giống Case 1, Bước 2-3
Bước 4: Bắt Đầu Database Transaction
- Mô tả: Bắt đầu một transaction cơ sở dữ liệu để đảm bảo tính nguyên tử
- Hành động: Gọi
$this->subscriptionRepository->transactionBegin() - Mục đích: Đảm bảo tất cả các thay đổi được áp dụng cùng nhau hoặc rollback khi có lỗi
Bước 5: Vô Hiệu Hóa Thành Viên (nếu được cung cấp)
- Mô tả: Cập nhật trạng thái của các thành viên được chỉ định thành inactive
- Hành động:
- Phân tích các ID thành viên phân tách bằng dấu phẩy:
explode(',', $request->input('members_to_inactive')) - Gọi
UserService::updateStatusByIds($memberIds, ActiveOrNotStatus::Inactive) - Thực thi:
UPDATE users SET status = 'inactive' WHERE id IN (?)
- Phân tích các ID thành viên phân tách bằng dấu phẩy:
- Lưu ý: Chỉ ảnh hưởng đến người dùng là thành viên của nhóm hiện tại (đã xác thực ở Bước 2)
Bước 6: Đặt Danh Sách Mong Muốn Sang Thủ Công (nếu được cung cấp)
- Mô tả: Cập nhật training_status của các danh sách mong muốn được chỉ định thành manual
- Hành động:
- Phân tích các slug danh sách mong muốn phân tách bằng dấu phẩy:
explode(',', $request->input('wishlists_to_manual')) - Gọi
WishlistToGroupService::changeTranningStatusBySlugs($wishlistSlugs, WishlistToGroupTraningStatus::Manual) - Thực thi:
UPDATE wishlist_to_groups SET training_status = 'manual' WHERE slug IN (?)
- Phân tích các slug danh sách mong muốn phân tách bằng dấu phẩy:
- Mục đích: Ngăn chặn huấn luyện tự động cho các danh sách mong muốn này
Bước 7: Commit Transaction
- Mô tả: Commit tất cả các thay đổi cơ sở dữ liệu
- Hành động: Gọi
$this->subscriptionRepository->transactionCommit() - Điều kiện thành công: Tất cả các cập nhật hoàn thành không có lỗi
Bước 8: Trả Về Phản Hồi Thành Công
- Mô tả: Trả về thông báo xác nhận cho client
- Cấu trúc phản hồi:
{ "status": true, "message": "プラン変更を確認しました。", "data": [] }
Các Bảng và Trường Cơ Sở Dữ Liệu Liên Quan
erDiagram
users {
bigint id PK
string name
string email
string status "Trạng thái tài khoản: active, inactive, suspended"
timestamp created_at
timestamp updated_at
}
group_members {
bigint id PK
bigint group_id FK
bigint user_id FK "Tham chiếu đến bảng users"
boolean is_creator "Cờ cho biết thành viên có là người tạo nhóm không"
timestamp created_at
}
wishlist_to_groups {
bigint id PK
bigint group_id FK
string slug "Slug của danh sách mong muốn"
string training_status "Trạng thái huấn luyện: auto, manual"
integer status "0: Inactive, 1: Active, 3: Canceled"
timestamp created_at
timestamp updated_at
}
subscriptions {
bigint id PK
bigint group_id FK
string status "Active, Canceled, Pending Cancellation, Past Due, Unpaid"
timestamp created_at
}
subscription_histories {
bigint id PK
bigint subscription_id FK
string type "Loại đăng ký"
string payment_status
timestamp created_at
}
users ||--o{ group_members : has
group_members }o--|| wishlist_to_groups : manages
subscriptions ||--o{ subscription_histories : tracks
Xử Lý Lỗi
Ghi log:
- Tất cả lỗi được ghi với
logThrow($th) - Rollback transaction được ghi log để debug
Chi Tiết Lỗi:
| Mã Trạng Thái | Thông Báo Lỗi | Mô Tả |
|---|---|---|
| 401 | "未認証です。" | Người dùng không được xác thực |
| 403 | "アクセスが拒否されました。" | Người dùng không phải là chủ nhóm |
| 400 | "リクエストが正しくありません。" | Lỗi xác thực (ID thành viên hoặc slug danh sách mong muốn không hợp lệ) |
| 400 | "アクティブなサブスクリプションがありません。" | Không tìm thấy đăng ký đang hoạt động |
| 400 | "変更予定のプランがありません。" | Không tìm thấy thay đổi gói đang chờ xử lý |
| 400 | "プラン変更の確認に失敗しました。" | Lỗi transaction hoặc lỗi khác |
Ghi Chú Bổ Sung
Quy Tắc Kinh Doanh:
- Chỉ người tạo nhóm (chủ sở hữu) mới có thể truy cập các endpoint này
- Vô hiệu hóa thành viên chỉ ảnh hưởng đến thành viên không phải người tạo
- Vô hiệu hóa bắt buộc danh sách mong muốn là cần thiết khi vi phạm giới hạn tài nguyên
- Vô hiệu hóa tùy chọn danh sách mong muốn là cho việc vượt quá giới hạn max_product_group
- Tất cả các thao tác cơ sở dữ liệu sử dụng transaction để đảm bảo tính nhất quán dữ liệu
Các Cân Nhắc Về Hiệu Suất:
- Eager load các mối quan hệ để tránh truy vấn N+1
- Sử dụng phương thức helper
getLimits()từ model SubscriptionHistory - Lập chỉ mục trên cột
statuscho truy vấn nhanh hơn
Bảo Mật:
- Kiểm tra quyền trên mỗi yêu cầu
- Xác thực đầu vào ngăn chặn SQL injection
- Chỉ có thể sửa đổi tài nguyên thuộc sở hữu nhóm
Cải Tiến Trong Tương Lai:
- Thêm bản xem trước chênh lệch chi phí giữa các gói
- Hỗ trợ thao tác hàng loạt cho danh sách thành viên lớn
- Thêm khả năng rollback nếu người dùng đổi ý