# Export/Get方式切替 設計書 ## 背景・目的 現状 `Module1.bas` はPleasanterの **Export API**(CSV全件取得)でデータを取得し、CSVパース後にテーブルへ書き込んでいる。これに加えて **Get API**(`api-record-get-multi`)でデータを取得する方式を追加し、基本情報シートのセルで方式を切り替えて実行できるようにする。両方式の処理時間を比較できるよう、開始・終了時刻と経過秒数をシートに記録する。 対象件数は200件以下を想定し、ページング処理は考慮しない。 ## セル配置(基本情報シート) | セル | 内容 | |---|---| | D3 | ステータスメッセージ(「取込処理中...」「取込処理完了」) | | D4 | 処理開始時刻(`Now`) | | D5 | 処理終了時刻(`Now`) | | D6 | 経過秒数(`(D5 - D4) * 86400` の数値) | | D7 | 取得方式(`"Export"` または `"Get"`。未入力・それ以外の値は `"Export"` 扱い) | ## Filter・Sorter条件(基本情報シート、Export/Get共通) Pleasanter APIの `ColumnFilterHash` / `ColumnSorterHash` に渡す条件を、基本情報シート上の表で指定できるようにする。Export方式・Get方式の両方で同じ条件を使う。 ### セル配置 | 表 | 見出し | データ行 | |---|---|---| | Filter表 | F2:G2(列名 / 値) | F3以降(列名が空白の行で終了) | | Sorter表 | H2:I2(列名 / 昇降順) | H3以降(`asc`または`desc`。列名が空白の行で終了) | - Filter表・Sorter表とも、データ行が1件も無ければ該当するHashはAPIリクエストに含めない - 両方とも0件の場合は `View` パラメータ自体をリクエストボディに含めない ### `SetupFilterSorterTables()`(新規・セットアップ用Sub) 基本情報シートのF2:G2、H2:I2を見出しとするExcelテーブル(ListObject)を新規作成する。`新版着工要因閲覧シートv0.1.xlsm` 上で手動実行することを想定したセットアップ専用処理(`run()` からは呼ばない)。既にテーブルが存在する場合は何もしない。 ### `BuildViewFromSheet() As Object`(新規・共通関数) 基本情報シートのFilter表・Sorter表を読み取り、APIリクエストの `View` に渡す Dictionary を組み立てて返す。Filter表・Sorter表がともに0件の場合は `Nothing` を返す。 - Filter表の各行から `colFilter.Add 列名, 値` で `ColumnFilterHash` 用Dictionaryを作る(1件以上ある場合のみ) - Sorter表の各行から `colSorter.Add 列名, 昇降順` で `ColumnSorterHash` 用Dictionaryを作る(1件以上ある場合のみ) - どちらか一方でもあれば `View` Dictionaryに `ColumnFilterHash` / `ColumnSorterHash` をそれぞれ追加(無い方は追加しない) - `exportCSVData` と `getRecordsData` の両方から呼び出し、`viewObj` が `Nothing` でなければ `apiBody.Add "View", viewObj` する ## 全体フロー(`Sub run()`) ``` Sub run() Call init ' defaultSh, tableId, apiKey, baseURL, fetchMethod(D7の値) をセット defaultSh.Range("D4").Value = Now defaultSh.Range("D3").Value = "取込処理中..." Application.ScreenUpdating = False Application.Calculation = xlManual Select Case fetchMethod Case "Get" Call runGetFlow Case Else Call runExportFlow End Select Application.Calculation = xlAutomatic Application.ScreenUpdating = True defaultSh.Range("D5").Value = Now defaultSh.Range("D6").Value = (defaultSh.Range("D5").Value - defaultSh.Range("D4").Value) * 86400 defaultSh.Range("D3").Value = "取込処理完了" End Sub ``` - `init()` に `fetchMethod = defaultSh.Range("D7").Value` の読み込みを追加する。 - `runExportFlow` は既存の `exportCSVData` → `exportCSVDataToSheet` 呼び出しをそのまま移設したもの(ロジック無変更)。 - `runGetFlow` は新規追加の `getRecordsData` → `getRecordsDataToSheet` を呼ぶ。 ## Export方式(Filter/Sorter対応のため一部変更) `exportCSVDataToSheet` / `ParseCsv` / `CollectionToArray` は変更しない。 `exportCSVData` のみ、既存のコメントアウトされた `colFilter` / `view` 部分を `BuildViewFromSheet()` 呼び出しに置き換える。 ``` Dim viewObj As Object Set viewObj = BuildViewFromSheet() If Not viewObj Is Nothing Then apiBody.Add "View", viewObj End If ``` (旧来の `yearFilter` を使ったコメントアウトコードは削除する) ## Get方式(新規追加) ### `getRecordsData(tableId As String) As Collection` Pleasanterの `api-record-get-multi` を呼び出し、レコード配列(Collection of Dictionary)を返す。 - エンドポイント: `POST {baseURL}/pleasanter/api/items/{tableId}/get` - リクエストボディ: `ApiVersion`, `ApiKey` - 取得件数は指定しない。1回のリクエストでの取得上限はサーバー側既定値(`Api.json` の `PageSize`、既定200件)であり、クライアント側から `PageSize` を指定するパラメータは存在しない。200件以下想定のため既定動作のまま1回のリクエストで取得しきる(`Offset` も指定不要) - レスポンス: `res("Response")("Data")` を返す(レコードDictionaryの配列。`Results` という入れ子は存在しない) - 既存の `callRestApi` をそのまま利用する - Filter/Sorter条件は `BuildViewFromSheet()` を呼び出し、`viewObj` が `Nothing` でなければ `apiBody.Add "View", viewObj` する(Export方式と同じ共通関数を利用) ### レコードのフラット化(`FlattenRecord(record As Dictionary) As Dictionary`) Pleasanterのget APIレスポンスは、`ClassA`〜`ClassZ` などの項目が `ClassHash`(同様に `NumHash`, `DateHash`, `DescriptionHash`, `CheckHash` など)という**入れ子のDictionary**の中に格納されている。トップレベルキーをそのままヘッダにすると、これらHash系フィールドが展開されずネストしたDictionaryのまま1セルに入ってしまう。 そのため `getRecordsDataToSheet` に渡す前に、各レコードをフラット化する処理を挟む。 - レコードDictionaryの各キー・値を走査する - 値が `Dictionary` である場合(`ClassHash` など `*Hash` 系フィールド)は、そのキー自体は使わず、**中身のキー・値をそのままフラット化後のDictionaryにマージ**する(例: `ClassHash("ClassA")` → フラット化後の `ClassA` キー) - 値が `Dictionary` でない単純値(`Title`, `Body`, `Status`, `ResultId` など)はそのままキー・値を引き継ぐ - 全レコードに対して同じ処理を行う ### `getRecordsDataToSheet(records As Collection, targetSheet As String)` `exportCSVDataToSheet` とは完全に独立した新規関数として実装する(既存ロジックへの影響を避けるため共通化しない)。 - `records.count = 0` の場合はメッセージ表示して終了 - 各レコードを `FlattenRecord` でフラット化する - ヘッダは **フラット化後の1件目レコードのキー一覧をそのまま使用**(`Title`, `Body`, `ClassA`, `DateA` などPleasanterの内部キー名。日本語ラベルへの変換は行わない) - 各レコード(フラット化後)の値をヘッダ順に2次元配列へ格納 - シート・テーブル(ListObject)の取得/新規作成、ヘッダ設定、`DataBodyRange` のクリアと書き込みは `exportCSVDataToSheet` と同様の手順を独自に実装する - テーブル名・出力先シートは `targetSheet`(=`tableId`)を共用し、Export方式実行時に作られた既存テーブルを上書きする(方式を切り替えるたびにヘッダ列構成が変わる点は許容する) ## 変更対象・追加対象まとめ - 変更: `init()`(`fetchMethod` 読み込み追加), `Sub run()`(分岐フローに書き換え), `exportCSVData`(`View` 組み立てを `BuildViewFromSheet()` 呼び出しに置き換え) - 追加: `runExportFlow`, `runGetFlow`, `getRecordsData`, `getRecordsDataToSheet`, `FlattenRecord`, `BuildViewFromSheet`, `SetupFilterSorterTables` - 無改造: `exportCSVDataToSheet`, `ParseCsv`, `CollectionToArray`, `callRestApi` ## 非対象・考慮しないこと - 200件を超えるページング処理 - Get方式でのヘッダの日本語ラベル化・Export方式との列対応付け - Get方式・Export方式のテーブルを別シートに分離すること(同一テーブルを共用する)