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_creator củ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 subscriptions cho đăng ký với status = 'active'group_id khớ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_history mới nhất với payment_status = 'paid' (sắp xếp theo paid_at DESC)
  • Gói mục tiêu: Bản ghi subscription_history mới nhất với type = 'change'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
  • Các trường phản hồi:
    • is_over_limit: boolean
    • current_member_count: integer
    • current_member_limit: từ gói hiện tại
    • new_member_limit: từ gói mục tiêu
    • excess_member_count: integer
    • members_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:
      • wishlistProducts
      • wishlistCategories
      • wishlistSearchQueries
      • wishlistCategoryViewpointReference (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_product
        • categories > max_category
        • search_queries > max_search_query
        • viewpoints > 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
  • Các trường phản hồi:
    • is_over_limit: boolean
    • total_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ên
    • total_excess: danh sách mong muốn hợp lệ vượt quá
    • force_deactivation: mảng với lý do
    • optional_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 (?)
  • Đ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 (?)
  • 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 (?)
  • 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:

  1. Chỉ người tạo nhóm (chủ sở hữu) mới có thể truy cập các endpoint này
  2. 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
  3. 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
  4. 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
  5. 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 status cho 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 ý