# バッチ設計書 16-check_stale_stock

## 概要

本ドキュメントは、InvenTreeシステムにおける在庫品の期限切れチェックバッチ処理について記述します。このバッチは、有効期限が設定された在庫品について、期限切れが近づいているもの（stale stock）を検出し、該当部品を購読しているユーザーに通知を送信します。

### 本バッチの処理概要

**業務上の目的・背景**：在庫管理において、消耗品や有効期限のある部品の期限管理は重要な業務課題です。期限切れの在庫を使用すると品質問題や安全上のリスクにつながる可能性があります。また、期限が近い在庫を早期に把握することで、優先的な使用や廃棄処理の計画が可能になります。本バッチは、期限が近い在庫品を自動検出し、関係者に通知することで、これらの課題を解決します。

**バッチの実行タイミング**：日次（毎日1回）。Django-Qスケジューラによりスケジュールされ、バックグラウンドワーカーによって実行されます。

**主要な処理内容**：
1. 在庫期限機能（STOCK_ENABLE_EXPIRY）の有効確認
2. STOCK_STALE_DAYS設定値の取得（期限切れ警告日数）
3. 在庫中（IN_STOCK）かつ有効期限が閾値内の在庫品を検索
4. 検索結果をユーザー単位でグループ化
5. 各ユーザーに対して、その人が購読している部品の期限切れ在庫一覧を含む通知を送信

**前後の処理との関連**：本バッチは独立して動作します。通知システムと連携し、メール通知やUI通知を生成します。

**影響範囲**：stock_stockitemテーブルを読み取り、通知テーブルに対して挿入を行います。在庫データ自体の変更は行いません。

## バッチ種別

通知配信

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 日次 |
| 実行時刻 | Django-Qスケジューラによる自動実行 |
| 実行曜日 | 毎日 |
| 実行日 | - |
| トリガー | cron（Django-Qスケジューラ） |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| Django-Qワーカー稼働中 | バックグラウンドワーカーが稼働している必要がある |
| データベース接続可能 | StockItem、通知テーブルへのアクセスが可能であること |
| STOCK_ENABLE_EXPIRY有効 | 在庫期限機能が有効化されていること |
| STOCK_STALE_DAYS > 0 | 期限切れ警告日数が1以上に設定されていること |

### 実行可否判定

- STOCK_ENABLE_EXPIRY設定がFalseの場合は処理をスキップ（ログ出力）
- STOCK_STALE_DAYS設定が0以下の場合は処理をスキップ（ログ出力）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | 本バッチはパラメータを取らない |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| グローバル設定 STOCK_ENABLE_EXPIRY | DB | 在庫期限機能の有効/無効 |
| グローバル設定 STOCK_STALE_DAYS | DB | 期限切れ警告日数 |
| stock_stockitem | DB | 在庫品テーブル |
| 部品購読情報 | DB | 各部品の購読ユーザー情報 |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| 通知テーブル | DB | ユーザーへの期限切れ警告通知 |
| メール | メッセージ | 購読ユーザーへのメール通知（設定による） |
| ログ出力 | テキスト | 処理状況のログメッセージ |

### 出力ファイル仕様

ファイル出力なし

## 処理フロー

### 処理シーケンス

```
1. バッチ開始
   └─ 設定確認フェーズ
2. STOCK_ENABLE_EXPIRY設定確認
   └─ Falseの場合は"Stock expiry functionality is not enabled"をログ出力して終了
3. STOCK_STALE_DAYS設定確認
   └─ 0以下の場合は"Stock stale days is not configured or set to 0"をログ出力して終了
4. 閾値日付計算
   └─ today + STOCK_STALE_DAYS = stale_threshold
5. 期限切れ間近の在庫検索
   └─ IN_STOCK_FILTERに合致
   └─ expiry_dateがnullでない
   └─ expiry_dateがstale_threshold未満
6. 結果確認
   └─ 該当なしの場合は"No stale stock items found"をログ出力して終了
7. ユーザー別グループ化
   └─ 各在庫品の部品購読者を取得
   └─ ユーザー別に在庫品リストを構築
8. 通知送信
   └─ 各ユーザーに対してnotify_stale_stockタスクをオフロード
9. バッチ終了
   └─ "Scheduled stale stock notifications for {n} users"をログ出力
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B{STOCK_ENABLE_EXPIRY<br/>有効?}
    B -->|無効| C[ログ出力して終了]
    B -->|有効| D{STOCK_STALE_DAYS<br/>> 0?}
    D -->|いいえ| E[ログ出力して終了]
    D -->|はい| F[閾値日付計算]
    F --> G[期限切れ間近の在庫検索]
    G --> H{該当在庫<br/>あり?}
    H -->|なし| I[ログ出力して終了]
    H -->|あり| J[ユーザー別にグループ化]
    J --> K[各ユーザーへ通知タスク登録]
    K --> L[ログ出力: 通知スケジュール完了]
    L --> M[バッチ終了]
    C --> M
    E --> M
    I --> M
```

## データベース操作仕様

### 操作別データベース影響一覧

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 設定取得 | common_settings | SELECT | STOCK_ENABLE_EXPIRY, STOCK_STALE_DAYS取得 |
| 在庫検索 | stock_stockitem | SELECT | 期限切れ間近の在庫検索 |
| 購読者取得 | part_partstar等 | SELECT | 部品の購読ユーザー取得 |
| 通知作成 | 通知テーブル | INSERT | 通知レコードの作成（非同期タスク内） |

### テーブル別操作詳細

#### stock_stockitem

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | expiry_date, part, location等 | IN_STOCK_FILTER, expiry_date IS NOT NULL, expiry_date < threshold | select_relatedで最適化 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 設定未設定 | STOCK_ENABLE_EXPIRYが未設定 | ログ出力後にスキップ |
| - | 設定値不正 | STOCK_STALE_DAYSが0以下 | ログ出力後にスキップ |
| - | 通知送信エラー | 通知タスクのオフロード失敗 | エラーログ出力して継続 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Django-Qのデフォルト設定に依存 |
| リトライ間隔 | Django-Qのデフォルト設定に依存 |
| リトライ対象エラー | 一時的なデータベースエラー |

### 障害時対応

1. ログを確認してエラー原因を特定
2. 設定値を確認
3. 必要に応じて手動で再実行

## トランザクション仕様

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | 読み取り専用（通知は非同期タスク） |
| コミットタイミング | - |
| ロールバック条件 | - |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 数百〜数千件の在庫検索 |
| 目標処理時間 | 2分以内 |
| メモリ使用量上限 | 特に制限なし |

## 排他制御

- 読み取り専用のため同時実行可能
- ただし、通常は日次1回の実行のため同時実行は想定しない

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 情報ログ | 機能無効時 | "Stock expiry functionality is not enabled - exiting" |
| 情報ログ | 設定未設定時 | "Stock stale days is not configured or set to 0 - exiting" |
| 情報ログ | 該当なし時 | "No stale stock items found" |
| 情報ログ | 検索完了時 | "Found {n} stale stock items" |
| 情報ログ | 通知完了時 | "Scheduled stale stock notifications for {n} users" |
| エラーログ | 通知失敗時 | "Error scheduling stale stock notification for user {username}: {error}" |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| タスク失敗 | 1回 | システム管理者 |
| 処理時間 | 5分超過 | システム管理者 |

## 備考

- 通知はユーザーごとに統合され、1ユーザー1通知となる
- 通知メールには期限までの日数や期限切れ状態が含まれる
- notify_stale_stock関数で詳細な通知内容を生成
- メールテンプレート: email/stale_stock_notification.html
- OpenTelemetryによるトレーシングに対応
- IN_STOCK_FILTERは在庫中の状態を示すフィルタ条件
