売上通知
コマンドシグネチャ
php artisan notification:sale-one-month
目的
notification:sale-one-monthコマンドは、アクティブなサブスクリプションを持つグループの通知設定を処理して、ウィッシュリスト製品およびグループの売上変化を検出します。このコマンドは通知設定をチャンク単位で取得し、設定されたしきい値(比率型または金額型)に基づいて売上データの変化を分析するバックグラウンドジョブをディスパッチし、条件が満たされた場合にグループメンバーに通知を送信します。これにより、ユーザー定義の基準に基づいて製品および製品グループの売上パフォーマンスの変化を自動的に監視することが可能になります。
シーケンス図
ステップ1:コマンド初期化と設定取得
sequenceDiagram
participant System
participant SaleCommand as notification:sale-one-month
participant SettingRepo as NotificationSettingRepository
participant ConsoleDB[(gb_console.notification_settings)]
participant Logger
participant Slack
Note over System,Slack: コマンド初期化(毎日実行)
rect rgb(200, 255, 200)
Note right of System: 正常ケース - コマンド起動
System->>SaleCommand: コマンドを実行
SaleCommand->>Logger: コマンド開始をログ
SaleCommand->>SettingRepo: chunkDataSendNotification(callback, 100)
SettingRepo->>ConsoleDB: WHERE group has active subscriptionをクエリ
ConsoleDB-->>SettingRepo: 通知設定を返す
SettingRepo-->>SaleCommand: 100レコードのチャンクで処理
SaleCommand->>Logger: 数付きでチャンク処理開始をログ
end
rect rgb(255, 200, 200)
Note right of System: エラーハンドリング
rect rgb(255, 230, 230)
alt データベース接続エラー
SettingRepo->>Logger: データベース接続エラーをログ
SettingRepo->>Slack: データベースエラー通知を送信
else アクティブサブスクリプションなし
SettingRepo->>Logger: アクティブサブスクリプションが見つからないことをログ
end
end
end
ステップ2:ジョブディスパッチとレコード処理
sequenceDiagram
participant SaleCommand as notification:sale-one-month
participant NotificationJob as SendNotificationJob
participant QueueSystem as Laravel Queue
participant Logger
participant Slack
Note over SaleCommand,Slack: バッチジョブ処理
rect rgb(200, 255, 200)
Note right of SaleCommand: 正常ケース - ジョブディスパッチ
loop 各チャンク(100レコード)に対して
SaleCommand->>QueueSystem: SendNotificationJob(records)をディスパッチ
QueueSystem->>NotificationJob: 処理のためにジョブをキューに入れる
SaleCommand->>Logger: チャンクサイズ付きでジョブディスパッチをログ
rect rgb(200, 230, 255)
Note right of NotificationJob: ジョブ初期化
NotificationJob->>NotificationJob: リポジトリを初期化
NotificationJob->>Logger: レコード数付きでジョブ開始をログ
loop チャンク内の各レコードに対して
NotificationJob->>NotificationJob: 処理タイプを決定(比率/金額)
NotificationJob->>Logger: レコード処理開始をログ
end
end
end
end
rect rgb(255, 200, 200)
Note right of SaleCommand: エラーハンドリング
rect rgb(255, 230, 230)
alt キューシステムエラー
SaleCommand->>Logger: キューディスパッチエラーをログ
SaleCommand->>Slack: キューエラー通知を送信
else ジョブ処理エラー
NotificationJob->>Logger: ジョブ処理エラーをログ
NotificationJob->>Slack: ジョブエラー通知を送信
end
end
end
ステップ3:比率タイプ処理 - 製品通知
sequenceDiagram
participant NotificationJob as SendNotificationJob
participant WishlistRepo as WishlistProductRepository
participant ProductRepo as ProductRepository
participant ProductDetailRepo as ProductDetailRepository
participant ConsoleDB[(gb_console)]
participant AnalyzerDB[(gb_analyzer)]
participant Logger
participant Slack
Note over NotificationJob,Slack: 比率タイプ - 製品処理
rect rgb(200, 255, 200)
Note right of NotificationJob: 正常ケース - 製品エンティティ処理
NotificationJob->>WishlistRepo: getEntity(notifiable_id)
WishlistRepo->>ConsoleDB: summaryProductを持つwishlist_productsをクエリ
ConsoleDB-->>WishlistRepo: ウィッシュリスト製品データを返す
WishlistRepo-->>NotificationJob: summaryProductを持つウィッシュリスト製品を返す
NotificationJob->>ProductRepo: getByMallProductId(summaryProduct.input)
ProductRepo->>AnalyzerDB: WHERE mall_product_idのproductsをクエリ
AnalyzerDB-->>ProductRepo: アナライザー製品を返す
ProductRepo-->>NotificationJob: アナライザー製品を返す
rect rgb(200, 230, 255)
Note right of NotificationJob: 日付範囲とデータ取得
NotificationJob->>NotificationJob: getDateRangeForFrequency(frequency)
NotificationJob->>ProductDetailRepo: getCurrentPeriodRecord(product, currentDateRange)
ProductDetailRepo->>AnalyzerDB: WHERE metrics_date IN rangeのproduct_detailsをクエリ
AnalyzerDB-->>ProductDetailRepo: 現在期間データを返す
ProductDetailRepo-->>NotificationJob: 現在期間の製品詳細を返す
NotificationJob->>ProductDetailRepo: getHistoricalRecord(product, historicalDateRange)
ProductDetailRepo->>AnalyzerDB: WHERE metrics_date IN historical rangeのproduct_detailsをクエリ
AnalyzerDB-->>ProductDetailRepo: 過去期間データを返す
ProductDetailRepo-->>NotificationJob: 過去期間の製品詳細を返す
end
rect rgb(255, 255, 200)
Note right of NotificationJob: 計算としきい値チェック
NotificationJob->>NotificationJob: calculateSalesValue(sales_one_month * price)
NotificationJob->>NotificationJob: calculate_percentage_change(current, previous)
NotificationJob->>NotificationJob: shouldNotifyForComparison(comparison_type, change, threshold)
NotificationJob->>Logger: 値付きで計算結果をログ
end
end
rect rgb(255, 200, 200)
Note right of NotificationJob: エラーハンドリング
rect rgb(255, 230, 230)
alt 製品が見つからない
NotificationJob->>Logger: mall_product_id付きで製品が見つからないことをログ
NotificationJob->>NotificationJob: レコードをスキップして継続
else 不十分なデータ
NotificationJob->>Logger: 不十分な売上データをログ
NotificationJob->>NotificationJob: レコードをスキップして継続
else 計算エラー
NotificationJob->>Logger: 入力データ付きで計算エラーをログ
NotificationJob->>Slack: 計算エラー通知を送信
end
end
end
ステップ4:比率タイプ処理 - グループ通知
sequenceDiagram
participant NotificationJob as SendNotificationJob
participant WishlistGroupRepo as WishlistToGroupRepository
participant ProductRepo as ProductRepository
participant ProductDetailRepo as ProductDetailRepository
participant ConsoleDB[(gb_console)]
participant AnalyzerDB[(gb_analyzer)]
participant Logger
participant Slack
Note over NotificationJob,Slack: 比率タイプ - グループ処理
rect rgb(200, 255, 200)
Note right of NotificationJob: 正常ケース - グループエンティティ処理
NotificationJob->>WishlistGroupRepo: getEntity(notifiable_id)
WishlistGroupRepo->>ConsoleDB: 製品を持つwishlist_groupsをクエリ
ConsoleDB-->>WishlistGroupRepo: 製品を持つウィッシュリストグループを返す
WishlistGroupRepo-->>NotificationJob: 製品を持つウィッシュリストグループを返す
NotificationJob->>NotificationJob: グループ製品からmall_product_idsを抽出
NotificationJob->>ProductRepo: getByMallProductIds(mallProductIds)
ProductRepo->>AnalyzerDB: WHERE mall_product_id IN (ids)のproductsをクエリ
AnalyzerDB-->>ProductRepo: アナライザー製品コレクションを返す
ProductRepo-->>NotificationJob: アナライザー製品コレクションを返す
rect rgb(200, 230, 255)
Note right of NotificationJob: 集計データ処理
NotificationJob->>NotificationJob: getDateRangeForFrequency(frequency)
NotificationJob->>ProductDetailRepo: getLatestProductDetailsInDateRange(productIds, currentRange)
ProductDetailRepo->>AnalyzerDB: 現在期間のproduct_detailsをクエリ
AnalyzerDB-->>ProductDetailRepo: 現在期間の詳細を返す
ProductDetailRepo-->>NotificationJob: 現在期間の詳細を返す
NotificationJob->>ProductDetailRepo: getLatestProductDetailsInDateRange(productIds, historicalRange)
ProductDetailRepo->>AnalyzerDB: 過去期間のproduct_detailsをクエリ
AnalyzerDB-->>ProductDetailRepo: 過去期間の詳細を返す
ProductDetailRepo-->>NotificationJob: 過去期間の詳細を返す
end
rect rgb(255, 255, 200)
Note right of NotificationJob: 集計計算
NotificationJob->>NotificationJob: calculateTotalSalesValue(currentDetails)
NotificationJob->>NotificationJob: calculateTotalSalesValue(historicalDetails)
NotificationJob->>NotificationJob: calculate_percentage_change(currentTotal, historicalTotal)
NotificationJob->>NotificationJob: shouldNotifyForComparison(comparison_type, change, threshold)
NotificationJob->>Logger: 集計計算結果をログ
end
end
rect rgb(255, 200, 200)
Note right of NotificationJob: エラーハンドリング
rect rgb(255, 230, 230)
alt グループ内に製品なし
NotificationJob->>Logger: 空の製品グループをログ
NotificationJob->>NotificationJob: レコードをスキップして継続
else 不十分な集計データ
NotificationJob->>Logger: 不十分な集計データをログ
NotificationJob->>NotificationJob: レコードをスキップして継続
end
end
end
ステップ5:金額タイプ処理
sequenceDiagram
participant NotificationJob as SendNotificationJob
participant WishlistRepo as Repository
participant ProductRepo as ProductRepository
participant ProductDetailRepo as ProductDetailRepository
participant ConsoleDB[(gb_console)]
participant AnalyzerDB[(gb_analyzer)]
participant Logger
participant Slack
Note over NotificationJob,Slack: 金額タイプ処理
rect rgb(200, 255, 200)
Note right of NotificationJob: 正常ケース - 金額しきい値処理
rect rgb(200, 230, 255)
alt notifiable_type = WishlistProduct
Note right of NotificationJob: 個別製品処理
NotificationJob->>WishlistRepo: getEntity(notifiable_id)
WishlistRepo->>ConsoleDB: wishlist_productsをクエリ
ConsoleDB-->>WishlistRepo: ウィッシュリスト製品を返す
WishlistRepo-->>NotificationJob: ウィッシュリスト製品を返す
NotificationJob->>ProductRepo: getByMallProductId(input)
ProductRepo->>AnalyzerDB: WHERE mall_product_idのproductsをクエリ
AnalyzerDB-->>ProductRepo: アナライザー製品を返す
ProductRepo-->>NotificationJob: アナライザー製品を返す
NotificationJob->>ProductDetailRepo: getLatestProductDetail()
ProductDetailRepo->>AnalyzerDB: 最新のproduct_detailsをクエリ
AnalyzerDB-->>ProductDetailRepo: 最新の製品詳細を返す
ProductDetailRepo-->>NotificationJob: 最新の製品詳細を返す
NotificationJob->>NotificationJob: calculateSalesValue(sales_one_month * price)
else notifiable_type = WishlistGroup
Note right of NotificationJob: グループ処理
NotificationJob->>WishlistRepo: getEntity(notifiable_id)
WishlistRepo->>ConsoleDB: 製品を持つwishlist_groupsをクエリ
ConsoleDB-->>WishlistRepo: ウィッシュリストグループを返す
WishlistRepo-->>NotificationJob: ウィッシュリストグループを返す
NotificationJob->>ProductRepo: getByMallProductIds(groupProductIds)
ProductRepo->>AnalyzerDB: WHERE mall_product_id IN (ids)のproductsをクエリ
AnalyzerDB-->>ProductRepo: アナライザー製品を返す
ProductRepo-->>NotificationJob: アナライザー製品を返す
NotificationJob->>ProductDetailRepo: getLatestProductDetailsForProducts(productIds)
ProductDetailRepo->>AnalyzerDB: 製品の最新product_detailsをクエリ
AnalyzerDB-->>ProductDetailRepo: 最新詳細コレクションを返す
ProductDetailRepo-->>NotificationJob: 最新詳細コレクションを返す
NotificationJob->>NotificationJob: calculateTotalSalesFromDetails(details)
end
end
rect rgb(255, 255, 200)
Note right of NotificationJob: しきい値評価
NotificationJob->>NotificationJob: shouldNotifyForThreshold(comparison_type, value, threshold)
NotificationJob->>Logger: しきい値比較結果をログ
end
end
rect rgb(255, 200, 200)
Note right of NotificationJob: エラーハンドリング
rect rgb(255, 230, 230)
alt 売上データ欠落
NotificationJob->>Logger: 売上データ欠落をログ
NotificationJob->>NotificationJob: レコードをスキップして継続
else 無効なしきい値
NotificationJob->>Logger: 無効なしきい値設定をログ
NotificationJob->>Slack: 設定エラー通知を送信
end
end
end
ステップ6:通知作成と配信
sequenceDiagram
participant NotificationJob as SendNotificationJob
participant GroupRepo as GroupRepository
participant NotificationSys as SendNotification
participant NotificationDB[(gb_console.notifications)]
participant PusherService as Pusher Service
participant Logger
participant Slack
Note over NotificationJob,Slack: 通知配信プロセス
rect rgb(200, 255, 200)
Note right of NotificationJob: 正常ケース - 通知条件が満たされた
NotificationJob->>NotificationJob: prepareNotificationData(entity, record, specificData)
NotificationJob->>GroupRepo: findById(group_id)
GroupRepo->>NotificationDB: groupMembers.userを持つgroupsをクエリ
NotificationDB-->>GroupRepo: メンバー付きグループを返す
GroupRepo-->>NotificationJob: groupMembers.userを持つグループを返す
NotificationJob->>NotificationJob: グループメンバーからユーザーを抽出
rect rgb(200, 230, 255)
Note right of NotificationJob: データベース保存
loop グループ内の各ユーザーに対して
NotificationJob->>NotificationSys: new SendNotification(data, product, type, action)
NotificationSys->>NotificationDB: 通知レコードを保存
NotificationDB-->>NotificationSys: 通知IDを返す
NotificationSys-->>NotificationJob: 通知インスタンスを返す
NotificationJob->>NotificationJob: NotificationSentイベントリスナーを登録
NotificationJob->>NotificationSys: ユーザーに通知を送信
NotificationSys-->>NotificationJob: NotificationSentイベントをトリガー
end
end
rect rgb(230, 200, 255)
Note right of NotificationJob: リアルタイム配信
NotificationJob->>NotificationSys: toPusher(userIds, notificationEvents)
NotificationSys->>PusherService: プライベートチャネルにリアルタイム通知を送信
PusherService-->>NotificationSys: 配信ステータスを確認
NotificationSys-->>NotificationJob: 配信ステータスを返す
NotificationJob->>Logger: ユーザー詳細付きで通知成功をログ
end
end
rect rgb(255, 200, 200)
Note right of NotificationJob: エラーハンドリング
rect rgb(255, 230, 230)
alt グループが見つからない
NotificationJob->>Logger: グループが見つからないエラーをログ
NotificationJob->>NotificationJob: 通知をスキップ
else グループにユーザーがいない
NotificationJob->>Logger: グループに有効なユーザーがいないことをログ
NotificationJob->>NotificationJob: 通知をスキップ
else データベース保存エラー
NotificationJob->>Logger: データベース保存エラーをログ
NotificationJob->>Slack: 保存エラー通知を送信
else Pusherサービスエラー
NotificationJob->>Logger: Pusherサービスエラーをログ
NotificationJob->>NotificationJob: データベースのみの配信にフォールバック
end
end
end
ステップ7:エラーハンドリングとコマンド完了
sequenceDiagram
participant NotificationJob as SendNotificationJob
participant SaleCommand as notification:sale-one-month
participant QueueSystem as Laravel Queue
participant Logger
participant Slack
Note over NotificationJob,Slack: エラーハンドリングとコマンド完了
rect rgb(255, 200, 200)
Note right of NotificationJob: エラーシナリオとリカバリー
rect rgb(255, 230, 230)
alt データベース接続エラー
NotificationJob->>Logger: 詳細付きでデータベース接続/クエリエラーをログ
NotificationJob->>NotificationJob: 指数バックオフリトライを実装
NotificationJob->>QueueSystem: 遅延付きでジョブを再試行
NotificationJob->>Slack: データベースエラーアラートを送信
else 製品データ欠落
NotificationJob->>Logger: mall_product_id付きで製品データ欠落をログ
NotificationJob->>NotificationJob: レコードをスキップして処理を継続
else 計算エラー
NotificationJob->>Logger: 入力データコンテキスト付きで計算エラーをログ
NotificationJob->>NotificationJob: レコードをスキップして処理を継続
NotificationJob->>Slack: 計算エラーアラートを送信
else Pusherサービスエラー
NotificationJob->>Logger: APIレスポンス付きでPusherサービスエラーをログ
NotificationJob->>NotificationJob: データベースのみの配信にフォールバック
NotificationJob->>Logger: フォールバック配信成功をログ
else ジョブタイムアウト
NotificationJob->>Logger: 処理統計付きでジョブタイムアウトをログ
NotificationJob->>QueueSystem: 再試行のためにジョブをリリース
NotificationJob->>Slack: タイムアウトアラートを送信
end
end
end
rect rgb(200, 255, 200)
Note right of SaleCommand: 正常ケース - コマンド完了
SaleCommand->>Logger: 統計付きでコマンド完了をログ
SaleCommand->>Logger: 処理された総チャンク数と送信された通知をログ
SaleCommand->>Logger: 実行時間とパフォーマンスメトリクスをログ
end
詳細
パラメータ
このコマンドには設定可能なパラメータはありません。すべての設定は以下を通じて処理されます:
- チャンクサイズ: 100レコードに固定(
CHUNK_LIMIT定数として定義) - 通知設定:
notification_settingsテーブルから取得 - アクティブサブスクリプション: アクティブサブスクリプションを持つグループのみを処理
頻度
- スケジュール実行:
routes/console.phpで設定された毎日の実行Schedule::command('notification:sale-one-month')->daily();
- 手動実行: テストや即時処理のために手動でトリガー可能
依存関係
データベース依存関係:
- 通知設定、グループ、およびユーザーデータ用のコンソールデータベース(
gb_console) - 製品詳細と売上メトリクス用のアナライザーデータベース(
gb_analyzer) - グループの
whereHas('group.activeSubscription')によるアクティブサブスクリプションステータス - 適切な関係を持つ有効なウィッシュリスト製品とグループ
外部サービス依存関係:
- リアルタイム通知配信用のPusherサービス
- 環境設定での有効なPusher認証情報
- バックグラウンドジョブ処理用のキューシステム
モデル依存関係:
- 適切なenum変換(Type、Frequency、ComparisonType)を持つ
NotificationSettingモデル - 通知可能エンティティのための
WishlistProductとWishlistGroupモデル - アナライザーデータベースからの
ProductとProductDetailモデル - 通知配信のための
GroupとGroupMemberモデル
出力
テーブル
完全なデータベーススキーマとテーブル関係については、通知概要のデータベーススキーマセクションを参照してください。
主要操作:
- 読み取り: アクティブなグループサブスクリプションを持つ
notification_settings - 読み取り: 現在および過去の期間の
product_detailsからの製品売上データ - 作成: 条件を満たす変更に対する
notificationsテーブルの新しいレコード - 送信: Pusherサービスを介したリアルタイム通知
サービス
通知処理:
SendNotificationJob: 通知チャンクを処理するためのバックグラウンドジョブSendNotification: データベース保存とPusher配信のためのLaravel通知クラス- コンソールとアナライザーデータベース間のデータアクセスのためのリポジトリパターン
計算ロジック:
- 比率タイプ: 現在と過去の期間の間のパーセンテージ変化を比較
- 売上値計算に
sales_one_month * priceを使用 - パーセンテージしきい値を持つ増加/減少比較タイプをサポート
- 売上値計算に
- 金額タイプ: 固定しきい値に対して絶対的な売上値を比較
- 金額しきい値を持つ超過/下回る比較タイプをサポート
エラーハンドリング
ログ
システムは監視とトラブルシューティングのためのログを生成します:
コマンド実行ログ:
- 実行コンテキスト付きのコマンド開始と完了
- チャンク処理統計とタイミング情報
- 完全な例外コンテキストとスタックトレース付きのエラー詳細
ジョブ処理ログ:
- 通知設定詳細付きの個別レコード処理結果
- 特定の製品識別子を持つ製品データ取得の成功/失敗
- 売上計算結果としきい値比較結果
- ユーザーとグループ詳細付きの通知配信確認
エラーカテゴリ:
- 再試行情報付きのデータベース接続失敗
- 特定のmall_product_id詳細付きの製品データ欠落
- 入力データコンテキスト付きの計算エラー
- APIレスポンス詳細付きのPusherサービス失敗
Slack
現在、実際のコードベースには実装されていません。エラー処理はLaravelの組み込みログシステムに依存しています。
トラブルシューティング
データを確認
アクティブな通知設定を確認:
-- アクティブサブスクリプションを持つ通知設定を確認
SELECT ns.id, ns.notifiable_type, ns.notifiable_id, ns.type,
ns.comparison_type, ns.comparison_value, ns.frequency,
g.name as group_name, s.status as subscription_status
FROM notification_settings ns
JOIN groups g ON ns.group_id = g.id
JOIN subscriptions s ON g.id = s.group_id
WHERE s.status = 'active'
ORDER BY ns.created_at DESC;
ウィッシュリスト製品とグループを確認:
-- 通知用のウィッシュリスト製品を検証
SELECT wp.id, wp.summaryProduct.input as mall_product_id,
wg.name as group_name, wp.created_at
FROM wishlist_products wp
JOIN wishlist_groups wg ON wp.wishlist_group_id = wg.id
WHERE wp.id IN (SELECT notifiable_id FROM notification_settings
WHERE notifiable_type = 'App\\Models\\WishlistProduct');
-- 通知用のウィッシュリストグループを検証
SELECT wg.id, wg.name, COUNT(wp.id) as product_count
FROM wishlist_groups wg
LEFT JOIN wishlist_products wp ON wg.id = wp.wishlist_group_id
WHERE wg.id IN (SELECT notifiable_id FROM notification_settings
WHERE notifiable_type = 'App\\Models\\WishlistGroup')
GROUP BY wg.id, wg.name;
製品売上データを確認:
-- 売上計算のための最近の製品詳細を確認
SELECT p.id, p.mall_product_id, pd.sales_one_month, pd.price,
(pd.sales_one_month * pd.price) as sales_value,
pd.metrics_date, pd.created_at
FROM products p
JOIN product_details pd ON p.id = pd.product_id
WHERE pd.metrics_date >= DATE_SUB(NOW(), INTERVAL 2 MONTH)
ORDER BY pd.metrics_date DESC, p.mall_product_id;
ログを確認
アプリケーションログ:
# 通知コマンド実行を確認
tail -f storage/logs/laravel.log | grep "notification:sale-one-month"
# SendNotificationJobの処理を確認
grep "SendNotificationJob" storage/logs/laravel.log | tail -20
# 通知配信を確認
grep "SendNotification" storage/logs/laravel.log | tail -10
# エラーパターンを確認
grep -E "(ERROR|Exception)" storage/logs/laravel.log | grep -i notification | tail -10
キューステータス:
# 通知ジョブのキューステータスを確認
php artisan queue:work --once --verbose
# 失敗したジョブを確認
php artisan queue:failed
# 失敗した通知ジョブを再試行
php artisan queue:retry all
データベース検証:
-- 作成された最近の通知を確認
SELECT n.id, n.type, n.notifiable_type, n.notifiable_id,
JSON_EXTRACT(n.data, '$.setting.wishlist_product_name') as product_name,
JSON_EXTRACT(n.data, '$.compare.change') as change_percentage,
n.read_at, n.created_at
FROM notifications n
WHERE n.created_at > DATE_SUB(NOW(), INTERVAL 1 DAY)
AND JSON_EXTRACT(n.data, '$.type') = 'sales_one_month_change'
ORDER BY n.created_at DESC;
-- 通知処理統計を確認
SELECT DATE(created_at) as notification_date,
COUNT(*) as total_notifications,
COUNT(CASE WHEN read_at IS NOT NULL THEN 1 END) as read_notifications
FROM notifications
WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY DATE(created_at)
ORDER BY notification_date DESC;