GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。 NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは 別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
14 KiB
LINEWORKS Form → プリザンター連携 設計書
日付: 2026-08-24(全面改訂)
背景・目的
LINEWORKS Form機能で収集したアンケート回答、プリザンターのテーブルへ連携。対象フォーム数、今後増える想定(部署ごとに追加等)。
初版設計(app-portal経由の単純な今すぐ実行ボタン、フォーム設定は固定コード管理)から、以下の運用要件が追加され全面改訂した:
- 使用する連携キー(LINEWORKS User Account認証情報)を、手動実行時にフォームごとに選択・記憶し、以降の自動実行はその記憶した連携キーを使う
- 一度も手動実行していないフォームは自動実行もスキップする(安全装置)
- 自動実行の周期もフォームごとに手動実行画面から設定できる
- フォームの質問→プリザンター列マッピングも、コードでなくプリザンター側で管理する(フォームごとに項目数・種類がまばらなため)
制約(調査結果)
- Form API呼び出し、User Account OAuth(Authorization Code)必須。Service Account/Bot(JWT)不可。scope:
form,form.read。 - Webhook/イベント通知なし。ポーリング必須。カーソルベースページング(
cursor,count最大1000)のみ、期間指定・差分取得パラメータなし。 GET /forms/{formId}/responses、フォーム作成者または共同管理者が呼べる。作成者本人限定でない。 → 連携専用LINEWORKSアカウントを対象フォームへ「共同管理者」として追加する運用で対応。- n8nのData Table機能、状態永続化用途で恒久使用禁止(ハングアップ元凶、feedback_n8n_datatable_prohibition)。→ n8nは状態を一切持たない設計とする。
- User Account OAuth認証部分は共通化。独立サービス[project_lineworks_user_auth]が認証UI・トークン管理・自動リフレッシュを一元提供。本アプリは
GET /token?key=<連携キー>を叩くだけ。 - 実データ確認済み(2026-08-24、
kenichiro.nogi連携キーで実機検証)。GET /forms/{formId}/responsesのレスポンス構造:- 各質問に固定
questionId(16進文字列)が付く → マッピングの識別子として使える respondent.emailが回答に含まれる → 照合キーとして使えるanswersは常に配列(単一選択でも1要素、複数選択は複数要素、未回答は空配列)- 確認済み
questionType:SINGLE_CHOICE/MULTI_CHOICE/TEXT(公式ドキュメント上は10種類、他タイプは未確認)
- 各質問に固定
- LINEWORKS Refresh Tokenの有効期限は90日(公式ドキュメント確認済み、project_lineworks_user_auth参照)。自動実行の周期は、90日以内に最低1回リフレッシュが発生する前提で設計する。
全体アーキテクチャ
[人] --ブラウザ--> [Node.js管理画面(lineworks-form-sync、/manage/*)]
├─ フォーム一覧・連携キー/周期設定(lineworks-user-authのGET /keysを参照)
├─「今すぐ実行」→その場で処理実行、設定保存、手動実行済みフラグON
└─「テスト回答取得」→質問マッピング雛形を自動生成・保存
[n8n Scheduleトリガー(固定頻度、例:15分ごと)] --> POST /execute (専用実行キー)
→ 対象フォームをループ、各フォームの周期・手動実行済みフラグを見て
実行すべきものだけ処理(自動/手動で共通の実処理ロジックを使う)
[app-portal] --web型「開く」ボタン--> 管理画面URLへ遷移(単なる入口、任意)
[Node.js /execute 処理本体]
│
GET /token?key=<フォームごとに選択済みの連携キー> → [lineworks-user-auth]
│
├→ LINEWORKS Form API(回答一覧・cursor)
├→ LINEWORKS添付ファイルAPI(同トークン)
└→ プリザンター Items API(respondent.emailで検索→更新 or 新規)
役割分担
| コンポーネント | 役割 |
|---|---|
| n8n | Scheduleトリガーのみ(固定頻度)。/executeを叩くだけ、状態は一切持たない |
Node.js新規アプリ(apps/lineworks-form-sync) |
管理画面(手動実行・設定UI)+実処理本体(自動/手動共通ロジック)。全ての設定・状態はプリザンター側で管理 |
| lineworks-user-auth(project_lineworks_user_auth) | OAuth認証UI・トークン管理・自動リフレッシュ。今回GET /keys(連携キー一覧API)を追加実装 |
| app-portal | 管理画面への入口リンクのみ(web型「開く」ボタン、任意) |
データ管理(プリザンター側へ全て集約、n8n Data Table不使用)
1. フォーム管理テーブル(新規、1行=1フォーム)
| 列 | 内容 | 設定者 |
|---|---|---|
| FormId | LINEWORKS Form ID | 人(先に手動入力) |
| 回答格納先SiteId | 保存先プリザンターテーブルのSiteId | 人(先に手動入力) |
| ユーザー照合列 | 保存先テーブルのうち、回答者メールアドレスで突合する列の物理列名 | 人(手動入力) |
| 連携キー | lineworks-user-authの連携キー名 | 管理画面(手動実行時にプルダウン選択・記憶) |
| 周期 | 15分/30分/1時間/3時間/6時間/1日 | 管理画面(手動実行時にプルダウン選択・記憶) |
| 質問マッピング | JSON配列(下記形式)、Description列に格納 | 管理画面「テスト回答取得」で雛形生成→人がtargetColumnを追記 |
| 手動実行済みフラグ | 未実行なら自動実行させない安全装置 | 管理画面「今すぐ実行」実行時にON |
| 最終手動実行日時 / 最終自動実行日時 | 監視用 | システムが更新 |
質問マッピングJSON形式(実データ確認済み、2026-08-24)
[
{ "questionId": "4642ccbd415f", "questionType": "SINGLE_CHOICE", "title": "節目検診・人間ドック希望の方", "targetColumn": "Class001" },
{ "questionId": "dfebd6143c6a", "questionType": "MULTI_CHOICE", "title": "オプションをご希望の方(複数選択可)", "targetColumn": "Class003" }
]
questionId/questionType/titleはLINEWORKS APIレスポンスのquestions配列からそのまま継承。description(質問の長文説明)・requiredは含めない(マッピング判断にtitleで十分、不要な長文を避ける)。targetColumnのみ本アプリで追加する4つ目のキー。
2. 回答格納先テーブル(フォームごと、既存方針通り)
ユーザー照合列(メールアドレス)を含む。質問マッピングのtargetColumnで指定された列群へ回答を格納。
3. LINEWORKS連携トークンテーブル(project_lineworks_user_auth管理、既存)
本アプリは直接触れない。lineworks-user-auth経由でのみアクセス。
マッピング雛形自動生成機能(新規)
管理画面の「テスト回答取得」ボタン:
- 対象フォームに選択済みの連携キー(未選択ならエラー、先に連携キー選択を促す)でaccess token取得
GET /forms/{formId}/responses(count=1程度)を呼び、最新1件の回答を取得。0件ならエラー表示- 取得した
questions配列からquestionId/questionType/titleを抽出 - 既存の質問マッピングとマージ: 既存
questionIdはtargetColumnを保持、新規questionIdのみtargetColumnを空文字列で追加 - フォーム管理テーブルの質問マッピング列へ保存
これにより、質問マッピングの骨格(質問ID・タイプ・タイトル)は自動取得され、人はtargetColumn(プリザンター列名)をプリザンター側UIで追記するだけで済む。フォームに質問が追加された場合も、再度「テスト回答取得」を実行すれば新規質問分だけ追加され、既存の対応関係は保持される。
投入ロジック(Upsert方式)
/execute(自動実行、n8n Scheduleから)実行時、対象フォームごとに:
- 手動実行済みフラグ確認 → falseならスキップ(未手動実行のフォームは自動実行しない)
- 前回実行日時 + 設定周期から実行時刻判定 → 未到来ならスキップ
- 連携キーのaccess token取得(
lineworks-user-authのGET /token経由) GET /forms/{formId}/responses呼び出し(cursorページング)- 各回答について、
respondent.emailで回答格納先テーブルを検索- 一致するレコードあり → 質問マッピングに従って該当列を更新
- 一致なし → ユーザー照合列にメールアドレスをセットしつつ、質問マッピングに従って新規作成
- 添付ファイルがある質問は、LINEWORKS添付ファイルAPI(同access token)でダウンロード→プリザンターItems APIの添付欄へアップロード
- 最終自動実行日時を更新
手動実行(管理画面「今すぐ実行」ボタン)は、上記1・2を省略して即実行。実行後に選択した連携キー・周期をフォーム管理テーブルへ保存し、手動実行済みフラグをON、最終手動実行日時を更新する。
questionType別 格納ルール(全10種類、実データ確認済み・2026-08-24)
サンプルフォーム(055dd7b8bbd336b1bbfd8fca982e3bb9)で全10種類のquestionTypeを実データ確認した。
| questionType | 実際のanswers例 | 格納ルール |
|---|---|---|
| SINGLE_CHOICE | ["A"] |
answers[0]をClass/Description型列へ |
| MULTI_CHOICE | ["B","A"] |
answers.join(',')をClass/Description型列へ |
| DROPDOWN | ["A"] |
answers[0]をClass/Description型列へ |
| TEXT | ["あ\nあああ"] |
answers[0]をClass/Description型列へ(改行含みうる) |
| RATING | ["3"] |
answers[0]をClass/Description型列へ(数値文字列のまま) |
| SINGLE_DATE(日程投票・単一) | ["2026-08-24T00:00:00+09:00"] |
answers[0]をClass/Description型列へ(Date型列非対応、範囲形式になりうるため) |
| MULTIPLE_DATE(日程投票・複数) | ["2026-08-25T00:00:00+09:00","2026-08-24T00:00:00+09:00"] |
answers.join(',')をClass/Description型列へ(Date型列非対応) |
| DATE_INPUT | ["2026-08-12T00:00:00+09:00"] |
targetColumnがDate型列ならanswers[0]をそのまま渡す。公式ドキュメントは「YYYY-MM-DD」形式と説明しているが、実データはDATETIME_INPUTと同じISO 8601形式(T00:00:00+09:00固定)で返る。ドキュメントでなく実データを正とする |
| DATETIME_INPUT | ["2026-08-24T02:30:00+09:00"] |
targetColumnがDate型列ならanswers[0]をそのまま渡す |
| ATTACHMENT | ["486838983"](attachmentId) |
GET /forms/{formId}/responses/{responseId}/attachments/{attachmentId}でダウンロード→プリザンター添付欄へアップロード。テキスト列への格納は行わない |
targetColumnの型判定: 物理列名のプレフィックス(Class/Description/Date)から判定する(pleasanterClient.jsのtoHashPayloadと同じprefix規則)。DATE_INPUT/DATETIME_INPUT以外の質問タイプでtargetColumnにDate*列が指定された場合はエラーとする(範囲・複数候補を保持できないため)。
エラーハンドリング・通知
/execute、失敗時に適切なHTTPステータス返却。n8n側でエラー分岐→SMTP通知(既存n8n-wf-admin@next-hd.co.jp)。
認証
- 管理画面(
/manage/*): project_lineworks_user_authのadminAuth.js/adminView.jsを完全踏襲(Master Key + セッションCookie + 60分限定の一時アクセスキー発行機能、確定)。 /execute(n8n→Node.js): 専用実行キーlineworks-user-authのGET /keys・GET /token: 本アプリ専用の実行キー(消費側ごとに別キー、既存方針project_org_master_sync_webhook_auth_keys踏襲)
lineworks-user-auth側への追加作業(本プロジェクトの一部として実施)
GET /keys(登録済み連携キー一覧を返すJSON API)を新規実装。管理画面の連携キー選択プルダウンがこれを参照する。実行キー認証で保護(または管理セッション保護、実装計画で確定)。
Node.jsアプリのエンドポイント一覧(暫定)
| パス | 呼び出し元 | 認証 | 役割 |
|---|---|---|---|
/health |
dokploy-webapp規約 | なし | ヘルスチェック(必須) |
/execute |
n8n | 専用実行キー | 対象フォームをループし、周期・フラグ判定→実処理(自動実行) |
/manage/* |
人(ブラウザ) | Master Key + セッション(一時キー可) | 手動実行ウィンドウ(フォーム一覧・連携キー/周期設定・今すぐ実行・テスト回答取得) |
app-portalは/manage(または管理画面トップ)への「開く」リンクとして登録(web型、PORTAL_APP_TYPE=web)。中継エンドポイントは不要(旧設計の/portal-triggerは廃止)。
n8n Scheduleトリガー頻度(確定)
15分ごと(固定)。全フォーム対象の最短周期選択肢(15分)に合わせる。実際の実行要否はNode.js側でフォームごとの周期・フラグを見て判定する。
未確定・今後の確認事項
- LINEWORKSユーザーマスタとの対応(ユーザー識別ID同士の紐付け)は別プロジェクト(LINEWORKSユーザーマスタ連携テーブル、計画中)で対応予定。本アプリは回答者メールアドレスでの直接突合のみ行い、マスタ連携には関与しない
- 対象フォームの具体的な一覧・作成部署(実装計画時に確定)