サブスクリプションプラン変更比較
概要説明
サブスクリプションプラン変更比較機能により、グループオーナーは新しいサブスクリプションプランへの変更を確認する前に、その影響をプレビューできます。ユーザーがプラン変更(アップグレードまたはダウングレード)をスケジュールすると、新しいプランのリソース制限が既存のグループメンバーとウィッシュリストにどのように影響するかを理解する必要があります。
この機能は、以下の詳細な比較を提供します:
- 現在のプランと対象プランのリソース制限
- 新しいメンバー制限を超えるメンバー(ダウングレード時)
- リソース制約に違反するウィッシュリスト
- 新しい商品グループ制限を超えるウィッシュリスト
このプロセスでは、グループオーナーが潜在的な影響を確認し、プラン変更を確認する前に、どのメンバーを無効にし、どのウィッシュリストを手動トレーニングステータスに設定するかについて、情報に基づいた決定を行う必要があります。
前提条件:
- ユーザーが認証されている必要があります
- ユーザーがグループ作成者(オーナー)である必要があります
- グループにアクティブなサブスクリプションがある必要があります
- 保留中のプラン変更(スケジュールされたプラン変更)が必要です
API: サブスクリプション比較変更API
アクティビティ図
---
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'>ユーザー権限の検証</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'>アクティブなサブスクリプションを取得</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'>プラン比較プレビューを取得</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'>メンバー差分を計算</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'>ウィッシュリスト差分を計算</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'>プレビューレスポンスを返す</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'>ユーザー権限の検証</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'>リクエストデータの検証</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'>トランザクション開始</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'>メンバーを無効化</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'>ウィッシュリストを手動に設定</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'>トランザクションをコミット</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
ケース1: 比較変更プレビュー取得 - 成功
説明
グループオーナーが現在のサブスクリプションプランと対象プランの変更のプレビューを正常に取得します。システムはメンバー制限とウィッシュリスト制約を分析して、潜在的な影響を特定します。
シーケンス図
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: 正常ケースフロー
Client->>Controller: GET /subscription/compare-change
rect rgb(255, 255, 200)
Note right of Controller: 権限チェック
Controller->>Controller: ユーザーがグループオーナーかチェック
end
rect rgb(200, 230, 255)
Note right of Controller: サブスクリプションデータ取得
Controller->>SubModel: アクティブなサブスクリプションを取得
SubModel->>Database: SELECT * FROM subscriptions WHERE group_id AND status = 'active'
Database-->>SubModel: アクティブなサブスクリプションを返す
SubModel-->>Controller: サブスクリプションオブジェクトを返す
Controller->>HistModel: 現在のプランを取得(最新の支払済み)
HistModel->>Database: SELECT * FROM subscription_histories WHERE payment_status = 'paid' ORDER BY paid_at DESC
Database-->>HistModel: 現在のプランを返す
Controller->>HistModel: 対象プランを取得(最新の変更保留中)
HistModel->>Database: SELECT * FROM subscription_histories WHERE type = 'change' AND payment_status = 'pending'
Database-->>HistModel: 対象プランを返す
end
rect rgb(200, 255, 255)
Note right of Controller: メンバー差分を計算
Controller->>Service: calculateMemberDifferences(group, currentPlan, targetPlan)
Service->>MemberModel: 現在のメンバー数を取得
MemberModel->>Database: SELECT COUNT(*) FROM group_members WHERE group_id
Database-->>MemberModel: カウントを返す
Service->>Service: 対象プランのmax_member制限と比較
Service->>MemberModel: 制限を超える場合、非作成者メンバーを取得
MemberModel->>Database: SELECT * FROM group_members WHERE group_id AND is_creator = false
Database-->>MemberModel: メンバーリストを返す
Service-->>Controller: メンバー差分を返す
end
rect rgb(230, 200, 255)
Note right of Controller: ウィッシュリスト差分を計算
Controller->>Service: calculateWishlistDifferences(group, currentPlan, targetPlan)
Service->>WishlistModel: 関連データを含むすべてのウィッシュリストを取得
WishlistModel->>Database: SELECT * FROM wishlist_to_groups WHERE group_id
Database-->>WishlistModel: ウィッシュリストを返す
Service->>Service: 各ウィッシュリストを対象制限に対してチェック
Service->>Service: 強制無効化を特定(リソース制限違反)
Service->>Service: 任意無効化を特定(max_product_group超過)
Service-->>Controller: ウィッシュリスト差分を返す
end
Controller-->>Client: 200 OK (比較データ)
end
rect rgb(255, 200, 200)
Note right of Client: エラーシナリオ
rect rgb(255, 230, 230)
alt グループオーナーではない
Controller-->>Client: 403 Forbidden
else アクティブなサブスクリプションなし
SubModel-->>Controller: アクティブなサブスクリプションが見つかりません
Controller-->>Client: 400 Bad Request
else 対象プランなし
HistModel-->>Controller: 保留中の変更が見つかりません
Controller-->>Client: 400 Bad Request
end
end
end
ステップ
ステップ1: 権限チェック
- 説明: リクエストを行っているユーザーがグループオーナーであることを確認
- 検証:
- ユーザーは認証されている必要があります(Bearerトークン)
- ユーザーの
groupMember.is_creatorがtrueである必要があります
- エラー条件: オーナーでない場合、メッセージ「アクセスが拒否されました。」で403 Forbiddenを返す
ステップ2: アクティブなサブスクリプションを取得
- 説明: グループの現在のアクティブなサブスクリプションを取得
- アクション:
subscriptionsテーブルからstatus = 'active'で一致するgroup_idのサブスクリプションをクエリ - エラー条件: アクティブなサブスクリプションが存在しない場合、メッセージ「アクティブなサブスクリプションがありません。」で400を返す
ステップ3: 現在および対象プランを取得
- 説明: 比較のためにサブスクリプション履歴レコードを取得
- 現在のプラン:
payment_status = 'paid'の最新のsubscription_historyレコード(paid_at DESCでソート) - 対象プラン:
type = 'change'およびpayment_status = 'pending'の最新のsubscription_historyレコード - エラー条件: 対象プランが存在しない場合、メッセージ「変更予定のプランがありません。」で400を返す
ステップ4: メンバー差分を計算
- 説明: 対象プランのメンバー制限が現在のメンバーにどのように影響するかを分析
- アクション:
group_membersテーブルから現在のメンバー数を取得target_plan.max_memberと比較- 制限を超える場合、
excess_member_count = current_count - max_memberを計算 - 無効化できる非作成者メンバー(
is_creator = false)のリストを取得
- レスポンスフィールド:
is_over_limit: booleancurrent_member_count: integercurrent_member_limit: 現在のプランからnew_member_limit: 対象プランからexcess_member_count: integermembers_to_choose: メンバーオブジェクトの配列(user_id、name、role)
ステップ5: ウィッシュリスト差分を計算
- 説明: 対象プランのリソース制限がウィッシュリストにどのように影響するかを分析
- アクション:
- 関連データを含むグループのすべてのウィッシュリストを取得:
wishlistProductswishlistCategorieswishlistSearchQuerieswishlistCategoryViewpointReference(ネストされたビューポイントデータを含む)
- 各ウィッシュリストについて、リソースをカウント:
- 商品数
- カテゴリ数
- 検索クエリ数
- ビューポイント数
- ウィッシュリストを分類:
- 強制無効化: 任意のリソース制限に違反
products > max_productcategories > max_categorysearch_queries > max_search_queryviewpoints > max_viewpoint
- 任意無効化: 有効なウィッシュリストだが
total_valid_wishlist > max_product_group
- 強制無効化: 任意のリソース制限に違反
- 関連データを含むグループのすべてのウィッシュリストを取得:
- レスポンスフィールド:
is_over_limit: booleantotal_wishlist: 合計数total_valid_wishlist: リソース要件を満たすウィッシュリストの数total_excess: 超過した有効なウィッシュリストforce_deactivation: 理由を含む配列optional_deactivation: 有効なウィッシュリストの配列
ステップ6: 比較レスポンスを返す
- 説明: 完全な比較データをフォーマットして返す
- レスポンス構造:
{ "status": true, "message": "プラン変更のプレビューを取得しました。", "data": { "current_plan": { "slug": "plan_slug", "name": "プラン名", "limits": { "max_member": 5, "max_product_group": 10, "max_product": 50, "max_category": 20, "max_search_query": 100, "max_viewpoint": 10 } }, "target_plan": { /* 同じ構造 */ }, "differences": { "members": { /* メンバー差分データ */ }, "wishlists": { /* ウィッシュリスト差分データ */ } } } }
データベース関連テーブルとフィールド
erDiagram
subscriptions {
bigint id PK
bigint package_id FK
bigint package_plan_id FK
bigint group_id FK "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 "subscriptionsテーブルへの参照"
bigint package_id FK
bigint package_plan_id FK
bigint group_id FK
bigint user_id FK
string old_plan_id "プラン変更時の古いパッケージプランデータ"
string type "サブスクリプションのタイプ"
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 "usersテーブルへの参照"
integer status "0: Inactive, 1: Active"
timestamp created_at
}
group_members {
bigint id PK
bigint group_id FK "groupsテーブルへの参照"
bigint user_id FK "usersテーブルへの参照"
bigint group_role_id FK
boolean is_creator "メンバーがグループ作成者かどうかを示すフラグ"
timestamp joined_at
timestamp created_at
}
wishlist_to_groups {
bigint id PK
bigint group_id FK "groupsテーブルへの参照"
bigint created_by FK "usersテーブルへの参照"
bigint subscription_id FK "subscriptionsテーブルへの参照"
string name "ウィッシュリストの名前"
string slug "ウィッシュリストのスラグ"
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
エラーハンドリング
ログ:
- すべてのエラーは
logThrow($th)でアプリケーションログに記録されます - 重大なエラーはSlack通知をトリガーする可能性があります
エラー詳細:
| ステータスコード | エラーメッセージ | 説明 |
|---|---|---|
| 401 | "未認証です。" | ユーザーが認証されていないまたは無効なトークン |
| 403 | "アクセスが拒否されました。" | ユーザーがグループオーナーではない |
| 400 | "アクティブなサブスクリプションがありません。" | グループのアクティブなサブスクリプションが見つかりません |
| 400 | "変更予定のプランがありません。" | 保留中のプラン変更が見つかりません |
| 400 | "プラン変更のプレビューに失敗しました。" | プレビュー生成中の一般的なエラー |
ケース2: 比較変更の確認 - 成功
説明
グループオーナーがプラン変更を確認し、指定されたメンバーの無効化とウィッシュリストのステータス変更を適用します。すべての操作はデータベーストランザクションでラップされ、データの一貫性を確保します。
シーケンス図
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: 正常ケースフロー
Client->>Controller: POST /subscription/confirm-change (members_to_inactive, wishlists_to_manual)
rect rgb(255, 255, 200)
Note right of Controller: 権限チェック
Controller->>Controller: ユーザーがグループオーナーかチェック
end
rect rgb(200, 230, 255)
Note right of Controller: リクエスト検証
Controller->>Request: validate(data)
Request->>Request: members_to_inactiveフォーマットを検証
Request->>GroupMemberModel: メンバーIDがグループに属しているかチェック
GroupMemberModel->>Database: SELECT user_id FROM group_members WHERE group_id AND user_id IN (...)
Database-->>GroupMemberModel: 一致するメンバーを返す
Request->>Request: wishlists_to_manualフォーマットを検証
Request->>WishlistModel: ウィッシュリストスラグがグループに属しているかチェック
WishlistModel->>Database: SELECT slug FROM wishlist_to_groups WHERE group_id AND slug IN (...)
Database-->>WishlistModel: 一致するウィッシュリストを返す
Request-->>Controller: 検証合格
end
rect rgb(200, 255, 255)
Note right of Controller: サブスクリプション状態を確認
Controller->>SubModel: アクティブなサブスクリプションを取得
SubModel->>Database: SELECT * FROM subscriptions WHERE group_id AND status = 'active'
Database-->>SubModel: アクティブなサブスクリプションを返す
Controller->>HistModel: 現在のプランを取得(最新の支払済み)
HistModel->>Database: SELECT * FROM subscription_histories WHERE subscription_id AND payment_status = 'paid'
Database-->>HistModel: 現在のプランを返す
Controller->>HistModel: 対象プランを取得(最新の変更保留中)
HistModel->>Database: SELECT * FROM subscription_histories WHERE subscription_id AND type = 'change'
Database-->>HistModel: 対象プランを返す
end
rect rgb(230, 200, 255)
Note right of Controller: トランザクションで変更を実行
Controller->>Service: executeCompareChange(members, wishlists)
Service->>Database: BEGIN TRANSACTION
alt 無効化するメンバー
Service->>UserService: updateStatusByIds(memberIds, 'inactive')
UserService->>GroupMemberModel: ユーザーステータスを更新
GroupMemberModel->>Database: UPDATE users SET status = 'inactive' WHERE id IN (...)
Database-->>GroupMemberModel: 更新を確認
end
alt 手動にするウィッシュリスト
Service->>WishlistService: changeTranningStatusBySlugs(wishlistSlugs, 'manual')
WishlistService->>WishlistModel: トレーニングステータスを更新
WishlistModel->>Database: UPDATE wishlist_to_groups SET training_status = 'manual' WHERE slug IN (...)
Database-->>WishlistModel: 更新を確認
end
Service->>Database: COMMIT TRANSACTION
Service-->>Controller: 成功結果を返す
end
Controller-->>Client: 200 OK (成功メッセージ)
end
rect rgb(255, 200, 200)
Note right of Client: エラーシナリオ
rect rgb(255, 230, 230)
alt グループオーナーではない
Controller-->>Client: 403 Forbidden
else 検証失敗
Request-->>Controller: 検証エラー
Controller-->>Client: 400 Bad Request (検証エラー)
else アクティブなサブスクリプションなし
SubModel-->>Controller: サブスクリプションが見つかりません
Controller-->>Client: 400 Bad Request
else 対象プランなし
HistModel-->>Controller: 保留中の変更なし
Controller-->>Client: 400 Bad Request
else トランザクションエラー
Database-->>Service: データベースエラー
Service->>Database: ROLLBACK TRANSACTION
Service-->>Controller: エラー結果
Controller-->>Client: 400 Bad Request
end
end
end
ステップ
ステップ1: 権限チェック
- 説明: リクエストを行っているユーザーがグループオーナーであることを確認
- ケース1のステップ1と同じ
ステップ2: リクエスト検証
- 説明: リクエストボディパラメータを検証
- 検証ルール:
members_to_inactive(オプション、文字列):- カンマ区切りのユーザーIDである必要があります
- すべてのユーザーIDは現在のグループに属している必要があります
- グループ作成者を含めることはできません
wishlists_to_manual(オプション、文字列):- カンマ区切りのウィッシュリストスラグである必要があります
- すべてのスラグは現在のグループに属している必要があります
- 検証クエリ:
- メンバー用:
SELECT user_id FROM group_members WHERE group_id = ? AND user_id IN (?) - ウィッシュリスト用:
SELECT slug FROM wishlist_to_groups WHERE group_id = ? AND slug IN (?)
- メンバー用:
- エラー条件:
- 無効なメンバーID: メッセージ「次のメンバーはあなたのグループに属していません: {ids}」で400を返す
- 無効なウィッシュリストスラグ: メッセージ「次のウィッシュリストはあなたのグループに属していません: {slugs}」で400を返す
ステップ3: サブスクリプション状態を確認
- 説明: サブスクリプションと対象プランが存在することを確認
- ケース1のステップ2-3と同じ検証
ステップ4: データベーストランザクションを開始
- 説明: アトミック性のためにデータベーストランザクションを開始
- アクション:
$this->subscriptionRepository->transactionBegin()を呼び出す - 目的: すべての変更が一緒に適用されるか、エラー時にロールバックされることを確保
ステップ5: メンバーを無効化(提供されている場合)
- 説明: 指定されたメンバーのステータスをinactiveに更新
- アクション:
- カンマ区切りのメンバーIDを解析:
explode(',', $request->input('members_to_inactive')) UserService::updateStatusByIds($memberIds, ActiveOrNotStatus::Inactive)を呼び出す- 実行:
UPDATE users SET status = 'inactive' WHERE id IN (?)
- カンマ区切りのメンバーIDを解析:
- 注意: 現在のグループのメンバーであるユーザーにのみ影響します(ステップ2で検証済み)
ステップ6: ウィッシュリストを手動に設定(提供されている場合)
- 説明: 指定されたウィッシュリストのtraining_statusをmanualに更新
- アクション:
- カンマ区切りのウィッシュリストスラグを解析:
explode(',', $request->input('wishlists_to_manual')) WishlistToGroupService::changeTranningStatusBySlugs($wishlistSlugs, WishlistToGroupTraningStatus::Manual)を呼び出す- 実行:
UPDATE wishlist_to_groups SET training_status = 'manual' WHERE slug IN (?)
- カンマ区切りのウィッシュリストスラグを解析:
- 目的: これらのウィッシュリストの自動トレーニングを防止
ステップ7: トランザクションをコミット
- 説明: すべてのデータベース変更をコミット
- アクション:
$this->subscriptionRepository->transactionCommit()を呼び出す - 成功条件: すべての更新がエラーなしで完了
ステップ8: 成功レスポンスを返す
- 説明: クライアントに確認メッセージを返す
- レスポンス構造:
{ "status": true, "message": "プラン変更を確認しました。", "data": [] }
データベース関連テーブルとフィールド
erDiagram
users {
bigint id PK
string name
string email
string status "アカウントステータス: active, inactive, suspended"
timestamp created_at
timestamp updated_at
}
group_members {
bigint id PK
bigint group_id FK
bigint user_id FK "usersテーブルへの参照"
boolean is_creator "メンバーがグループ作成者かどうかを示すフラグ"
timestamp created_at
}
wishlist_to_groups {
bigint id PK
bigint group_id FK
string slug "ウィッシュリストのスラグ"
string training_status "トレーニングステータス: 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 "サブスクリプションのタイプ"
string payment_status
timestamp created_at
}
users ||--o{ group_members : has
group_members }o--|| wishlist_to_groups : manages
subscriptions ||--o{ subscription_histories : tracks
エラーハンドリング
ログ:
- すべてのエラーは
logThrow($th)で記録されます - トランザクションロールバックはデバッグのために記録されます
エラー詳細:
| ステータスコード | エラーメッセージ | 説明 |
|---|---|---|
| 401 | "未認証です。" | ユーザーが認証されていない |
| 403 | "アクセスが拒否されました。" | ユーザーがグループオーナーではない |
| 400 | "リクエストが正しくありません。" | 検証エラー(無効なメンバーIDまたはウィッシュリストスラグ) |
| 400 | "アクティブなサブスクリプションがありません。" | アクティブなサブスクリプションが見つかりません |
| 400 | "変更予定のプランがありません。" | 保留中のプラン変更が見つかりません |
| 400 | "プラン変更の確認に失敗しました。" | トランザクションエラーまたはその他の失敗 |
追加メモ
ビジネスルール:
- グループ作成者(オーナー)のみがこれらのエンドポイントにアクセスできます
- メンバーの無効化は非作成者メンバーにのみ影響します
- ウィッシュリストの強制無効化は、リソース制限に違反した場合に必要です
- ウィッシュリストの任意無効化は、max_product_group制限を超える場合です
- すべてのデータベース操作はトランザクションを使用してデータの一貫性を確保します
パフォーマンスの考慮事項:
- N+1クエリを避けるために関係を積極的にロードします
- SubscriptionHistoryモデルから
getLimits()ヘルパーメソッドを使用します - 可能な場合はプランデータをキャッシュします
セキュリティ:
- すべてのリクエストで権限チェック
- 入力検証によりSQLインジェクションを防止
- グループが所有するリソースのみを変更できます
将来の機能拡張:
- プラン間のコスト差のプレビューを追加
- 大規模なメンバーリストの一括操作をサポート
- ユーザーが気が変わった場合のロールバック機能を追加