# バッチ設計書 8-update_exchange_rates

## 概要

本ドキュメントは、InvenTreeシステムにおけるupdate_exchange_ratesバッチ処理の設計仕様を定義します。このバッチは、通貨為替レートを外部APIから取得して更新し、複数通貨での価格計算を正確に行えるようにする機能を提供します。

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

**業務上の目的・背景**：InvenTreeは国際的な在庫管理に対応しており、部品の購入価格、販売価格、在庫評価額などを複数の通貨で管理できます。正確な通貨換算を行うためには、最新の為替レートが必要です。本バッチは、設定可能な間隔（デフォルト1日）で外部の為替レートプロバイダーから最新レートを取得し、システム内の為替レートテーブルを更新します。これにより、在庫評価や価格比較が常に最新の為替レートに基づいて行われます。

**バッチの実行タイミング**：日次でスケジュールされますが、実際の更新は設定可能な間隔（デフォルト1日）で実行されます。`check_daily_holdoff`関数により、設定間隔内に成功していれば更新をスキップします。

**主要な処理内容**：
1. 更新間隔設定`CURRENCY_UPDATE_INTERVAL`に基づき実行可否を判定（force=Trueの場合は強制実行）
2. InvenTreeExchangeバックエンドを使用して為替レートを更新
3. システムで設定されている通貨以外のレートを削除（クリーンアップ）
4. タスク成功を記録

**前後の処理との関連**：このバッチは独立して動作します。更新された為替レートは、部品価格計算、在庫評価、レポート生成などの機能で使用されます。

**影響範囲**：djmoney.contrib.exchange.Rateテーブルに影響します。為替レートの変更は、システム全体の価格計算に影響を与えます。

## バッチ種別

データ連携 / 外部API連携

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 日次（スケジュール）/ 実際は1日間隔（デフォルト） |
| 実行時刻 | Django-Qスケジューラ依存 |
| 実行曜日 | 毎日（ホールドオフロジックで制御） |
| 実行日 | 毎日（ホールドオフロジックで制御） |
| トリガー | Django-Qスケジューラ（DAILY） + check_daily_holdoff |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| Django-Qワーカー起動 | バックグラウンドワーカープロセスが稼働していること |
| データベース接続 | データベースへの接続が確立されていること |
| Appレジストリ準備完了 | Djangoアプリケーションレジストリが初期化されていること |
| インターネット接続 | 為替レートAPIへの接続が可能であること |

### 実行可否判定

- Django-Qワーカープロセスが起動している場合、日次でスケジュール実行
- `force=True`パラメータが指定された場合は強制実行
- `check_daily_holdoff`関数により、設定間隔（デフォルト1日）以内に成功していれば実行をスキップ
- `AppRegistryNotReady`例外が発生した場合は処理をスキップしログ出力

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| force | bool | No | False | Trueの場合、ホールドオフを無視して強制実行 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| 為替レートAPI | REST/JSON | InvenTreeExchange経由で外部APIから取得 |
| InvenTreeGlobalSetting | DB | CURRENCY_UPDATE_INTERVAL設定 |
| InvenTreeGlobalSetting | DB | デフォルト通貨設定 |
| InvenTreeGlobalSetting | DB | 有効通貨リスト |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| djmoney.contrib.exchange.Rate | DB | 為替レートテーブル |
| InvenTreeGlobalSetting | DB | タスク成功日時の記録 |
| ログ | テキスト | 処理状況をstructlogで出力 |

### 出力ファイル仕様

ファイル出力はありません。

## 処理フロー

### 処理シーケンス

```
1. モジュールのインポート
   └─ Rate, currency_code_default, currency_codes, InvenTreeExchangeをインポート
   └─ インポート失敗時は処理終了
2. 実行可否判定（force=Falseの場合）
   └─ CURRENCY_UPDATE_INTERVAL設定を取得（デフォルト1日）
   └─ check_daily_holdoffで実行可否を判定
3. 為替レート更新
   └─ InvenTreeExchangeバックエンドを初期化
   └─ デフォルト通貨を基準通貨として取得
   └─ update_rates()メソッドで最新レートを取得・更新
4. 不要レートの削除
   └─ システムで有効な通貨リスト（currency_codes()）を取得
   └─ リストに含まれない通貨のレートを削除
5. 成功記録
   └─ record_task_success('update_exchange_rates')を呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B{モジュールインポート成功?}
    B -->|No| C[ログ出力して終了]
    B -->|Yes| D{force=True?}
    D -->|Yes| F[為替レート更新へ]
    D -->|No| E[更新間隔設定を取得]
    E --> G{check_daily_holdoff通過?}
    G -->|No| H[スキップログ出力して終了]
    G -->|Yes| F
    F --> I[InvenTreeExchangeを初期化]
    I --> J[デフォルト通貨を取得]
    J --> K[update_ratesを実行]
    K --> L{更新成功?}
    L -->|No| M[エラーログ出力]
    L -->|Yes| N[有効通貨リストを取得]
    N --> O[不要レートを削除]
    O --> P[タスク成功を記録]
    P --> Q[バッチ終了]
    C --> R[処理終了]
    H --> R
    M --> R
    Q --> R
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 設定取得 | InvenTreeGlobalSetting | SELECT | 更新間隔・通貨設定を取得 |
| ホールドオフ確認 | InvenTreeGlobalSetting | SELECT | 前回実行日時を確認 |
| レート更新 | djmoney.contrib.exchange.Rate | INSERT/UPDATE | 為替レートを更新 |
| 不要レート削除 | djmoney.contrib.exchange.Rate | DELETE | 無効通貨のレートを削除 |
| 成功記録 | InvenTreeGlobalSetting | UPDATE | タスク成功日時を記録 |

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

#### djmoney.contrib.exchange.Rate

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | currency, value | 外部APIから取得したレート | InvenTreeExchangeバックエンドが実行 |
| DELETE | - | backend='InvenTreeExchange' AND currency NOT IN (有効通貨リスト) | 不要レートのクリーンアップ |

#### InvenTreeGlobalSetting

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | value | key='CURRENCY_UPDATE_INTERVAL' | デフォルト1日 |
| UPDATE | value | key='_update_exchange_rates_SUCCESS' | 成功日時をISO形式で保存 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | AppRegistryNotReady | Djangoアプリ初期化前に実行 | ログ出力後、処理をスキップ |
| - | OperationalError | データベース接続エラー | 警告ログ出力、次回実行で自動復旧 |
| - | ProgrammingError | データベーススキーマエラー | 警告ログ出力、次回実行で自動復旧 |
| - | Exception | 為替レートAPI接続エラーなど | エラーログ出力（スタックトレース含む） |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（明示的リトライなし） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

備考：check_daily_holdoffにより、失敗時は12時間以内の再試行を抑制

### 障害時対応

- AppRegistryNotReady発生時：次回スケジュール実行で自動復旧
- 為替レートAPI接続エラー：12時間後に自動リトライ（ホールドオフロジック）
- データベースエラー：データベース復旧後に自動復旧

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | InvenTreeExchange.update_rates()内部で管理 |
| コミットタイミング | 各操作完了時に自動コミット |
| ロールバック条件 | 例外発生時（ただし明示的なロールバック処理なし） |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 数十〜数百件（有効通貨数依存） |
| 目標処理時間 | 30秒以内（ネットワーク遅延依存） |
| メモリ使用量上限 | 50MB以下 |

## 排他制御

- 同時実行：Django-Qスケジューラにより1日1回のみ実行
- ロック仕様：check_daily_holdoffにより12時間以内の重複実行を防止

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 情報ログ | 更新スキップ時 | 'Skipping exchange rate update (interval not reached)' |
| 情報ログ | 更新開始時 | "Updating exchange rates using base currency '%s'" |
| 警告ログ | DB未準備時 | 'Could not update exchange rates - database not ready' |
| エラーログ | 例外発生時 | 'Error updating exchange rates: %s' |
| 情報ログ | Appレジストリ未準備時 | "Could not perform 'update_exchange_rates' - App registry not ready" |
| トレースログ | 処理開始時 | OpenTelemetryスパン 'update_exchange_rates' |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| API接続失敗 | 連続3回以上 | システム管理者 |
| 処理時間 | 60秒以上 | システム管理者 |
| 為替レート異常値 | 前回比30%以上の変動 | システム管理者（要実装） |

## 備考

- 設定`CURRENCY_UPDATE_INTERVAL`はInvenTree管理画面から変更可能（デフォルト1日）
- `force=True`パラメータは手動実行や特定条件での強制更新に使用
- InvenTreeExchangeはdjmoneyライブラリを拡張したカスタム為替バックエンド
- `currency_code_default()`は基準通貨を返す（システム設定で定義）
- `currency_codes()`は有効な通貨コードリストを返す
- 不要な通貨のレートを削除することで、データベースサイズを適切に管理
- `@tracer.start_as_current_span('update_exchange_rates')`デコレータにより、OpenTelemetryによる分散トレーシングに対応
- 実装ファイル: `src/backend/InvenTree/InvenTree/tasks.py`
