# 機能設計書 28-PluggyAI連携

## 概要

本ドキュメントは、Actual Budget における PluggyAI連携機能の設計仕様を記述する。この機能は、PluggyAI APIを使用してブラジルの銀行口座と連携し、取引データを自動的に取得することを目的とする。

### 本機能の処理概要

PluggyAI連携機能は、PluggyAI APIを通じてブラジルの金融機関の口座にアクセスし、取引データを自動取得する。PluggyAIはブラジルのOpen Finance規制に対応したサービスで、ブラジル国内の銀行口座との連携を提供する。

**業務上の目的・背景**：ブラジルでは独自のOpen Finance規制があり、GoCardlessやSimpleFINではカバーされない。PluggyAIはブラジル市場に特化したサービスを提供し、ブラジル在住のユーザーが銀行口座を連携できるようにする。

**機能の利用シーン**：
- ブラジルの銀行口座をActualに連携する
- 定期的に銀行取引を自動取得する
- クレジットカード取引の取得（金額符号反転処理あり）

**主要な処理内容**：
1. 設定状態の確認：PluggyAI clientIdの設定確認
2. 口座一覧の取得：ItemIDから連携された口座を取得
3. 取引の取得：指定期間の取引データを取得
4. 取引の正規化：ブラジル特有のデータ形式を標準形式に変換

**関連システム・外部連携**：
- PluggyAI API
- 同期サーバー（プロキシとして動作）

**権限による制御**：PluggyAI clientIdとclientSecretはサーバー側でシークレットとして管理される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 25 | 銀行同期管理画面（デスクトップ） | 主画面 | PluggyAI連携設定 |
| 26 | 銀行同期管理画面（モバイル） | 主画面 | モバイルでのPluggyAI連携管理 |

## 機能種別

データ連携 / 外部API連携

## 入力仕様

### 入力パラメータ

#### /status

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| - | - | - | パラメータなし | - |

#### /accounts

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| - | - | - | パラメータなし（itemIdsはサーバー設定） | - |

#### /transactions

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| accountId | string | Yes | 口座ID | PluggyAI形式 |
| startDate | string | Yes | 取得開始日 | YYYY-MM-DD |

### 入力データソース

- サーバー設定（PluggyAI clientId, clientSecret, itemIds）
- PluggyAI（口座情報、取引データ）

## 出力仕様

### 出力データ

#### /status レスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| configured | boolean | PluggyAIが設定されているか |

#### /accounts レスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| accounts | Account[] | 連携された口座リスト |

#### /transactions レスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| balances | Balance[] | 口座残高 |
| startingBalance | number | 開始残高（セント単位） |
| transactions.booked | Transaction[] | 確定取引 |
| transactions.pending | Transaction[] | 保留取引 |
| transactions.all | Transaction[] | 全取引 |

### 出力先

- クライアント：取引一覧、残高情報
- ローカルDB：インポートされた取引

## 処理フロー

### 処理シーケンス

```
1. 設定確認
   └─ clientIdの存在確認

2. 口座取得
   └─ itemIdsからItemID一覧を取得
   └─ 各ItemIDの口座を取得
   └─ 口座一覧を集約

3. 取引取得
   └─ 口座IDで取引を取得
   └─ 口座タイプに応じて金額を調整（クレジットカードは符号反転）
   └─ 取引を正規化してレスポンス
```

### フローチャート

```mermaid
flowchart TD
    A[PluggyAI連携開始] --> B{clientId設定済み?}
    B -->|No| C[未設定エラー]
    B -->|Yes| D[/accounts]
    D --> E[itemIds取得・分割]
    E --> F[各itemIdの口座取得]
    F --> G[口座一覧集約]
    G --> H[口座選択・紐付け]
    H --> I[/transactions]
    I --> J[取引取得]
    J --> K{口座タイプ=CREDIT?}
    K -->|Yes| L[金額符号反転]
    K -->|No| M[そのまま]
    L --> N[取引正規化]
    M --> N
    N --> O[Actualに取引登録]
    O --> P[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-28-1 | clientId必須 | PluggyAI clientIdがサーバーに設定されている必要がある | 全API呼び出し時 |
| BR-28-2 | クレジットカード金額反転 | 口座タイプがCREDITの場合、金額と残高を符号反転 | 取引取得時 |
| BR-28-3 | ItemID複数対応 | カンマ区切りで複数ItemIDを指定可能 | 口座取得時 |
| BR-28-4 | PENDING状態判定 | status==='PENDING'でpending判定 | 取引分類時 |

### 計算ロジック

**残高計算**:
```javascript
let startingBalance = parseInt(Math.round(account.balance * 100).toString());
if (account.type === 'CREDIT') {
  startingBalance = -startingBalance;
}
```

**金額計算**:
```javascript
if (account.type === 'CREDIT') {
  trans.amount *= -1;
  if (trans.amountInAccountCurrency) {
    trans.amountInAccountCurrency *= -1;
  }
}
let amountInCurrency = trans.amountInAccountCurrency ?? trans.amount;
amountInCurrency = Math.round(amountInCurrency * 100) / 100;
```

**支払先名取得**:
```javascript
function getPayeeName(trans) {
  // マーチャント名を優先
  if (trans.merchant && (trans.merchant.name || trans.merchant.businessName)) {
    return trans.merchant.name || trans.merchant.businessName;
  }
  // 支払データから取得
  if (trans.paymentData) {
    if (trans.type === 'DEBIT' && receiver) {
      return receiver.name || receiver.documentNumber?.value;
    }
    if (trans.type === 'CREDIT' && payer) {
      return payer.name || payer.documentNumber?.value;
    }
  }
  return '';
}
```

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

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

| 操作 | 対象 | 操作種別 | 概要 |
|-----|-----|---------|------|
| シークレット取得 | secretsテーブル | SELECT | clientId, itemIds取得 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| error | 一般エラー | API通信失敗等 | ログを確認 |

### リトライ仕様

- エラー時：エラーメッセージをレスポンスに含めて返却

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

- PluggyAI APIへのリクエストはトランザクション外

## パフォーマンス要件

- 口座取得：itemId数に依存
- 取引取得：期間に依存

## セキュリティ考慮事項

- clientIdとclientSecretはサーバー側のシークレットとして管理
- 通信はHTTPS

## 備考

- PluggyAIはブラジル市場に特化したサービス
- flattenObject関数でネストされた取引データをフラット化

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: APIエンドポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-pluggyai.js | `packages/sync-server/src/app-pluggyai/app-pluggyai.js` | 全APIエンドポイント |

**読解のコツ**:
- **14-27行目**: `/status` - 設定状態確認
- **29-60行目**: `/accounts` - 口座一覧取得、itemIdsから各口座を集約
- **62-171行目**: `/transactions` - 取引取得、クレジットカード金額反転
- **173-175行目**: `getDate` - 日付フォーマット
- **177-195行目**: `flattenObject` - ネストオブジェクトのフラット化
- **197-215行目**: `getPayeeName` - 支払先名取得ロジック

### プログラム呼び出し階層図

```
Client
    │
    ├─ POST /pluggyai/status
    │       │
    │       └─ secretsService.get(pluggyai_clientId)
    │
    ├─ POST /pluggyai/accounts
    │       │
    │       ├─ secretsService.get(pluggyai_itemIds)
    │       │
    │       └─ itemIds.split(',').map(item =>
    │               pluggyaiService.getAccountsByItemId(item))
    │
    └─ POST /pluggyai/transactions
            │
            ├─ pluggyaiService.getTransactions(accountId, startDate)
            │
            ├─ pluggyaiService.getAccountById(accountId)
            │
            ├─ (CREDIT口座) 金額符号反転
            │
            └─ flattenObject() → 正規化レスポンス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| app-pluggyai.js | `packages/sync-server/src/app-pluggyai/app-pluggyai.js` | ソース | APIエンドポイント |
| pluggyai-service.js | `packages/sync-server/src/app-pluggyai/pluggyai-service.js` | ソース | PluggyAI API通信 |
| secrets-service.js | `packages/sync-server/src/services/secrets-service.js` | ソース | シークレット管理 |
