ken_nogi/NodeSrv/docs/superpowers/specs/2026-08-24-lineworks-form-pleasanter-sync-design.md
Kenichiro NOGI ce58cb4be4 初回コミット: dev配下(NodeSrv/Pleasanter等)をGitea管理下に統合
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>
2026-09-04 15:37:06 +09:00

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経由でのみアクセス。

マッピング雛形自動生成機能(新規)

管理画面の「テスト回答取得」ボタン:

  1. 対象フォームに選択済みの連携キー(未選択ならエラー、先に連携キー選択を促す)でaccess token取得
  2. GET /forms/{formId}/responses(count=1程度)を呼び、最新1件の回答を取得。0件ならエラー表示
  3. 取得したquestions配列からquestionId/questionType/titleを抽出
  4. 既存の質問マッピングとマージ: 既存questionIdtargetColumnを保持、新規questionIdのみtargetColumnを空文字列で追加
  5. フォーム管理テーブルの質問マッピング列へ保存

これにより、質問マッピングの骨格(質問ID・タイプ・タイトル)は自動取得され、人はtargetColumn(プリザンター列名)をプリザンター側UIで追記するだけで済む。フォームに質問が追加された場合も、再度「テスト回答取得」を実行すれば新規質問分だけ追加され、既存の対応関係は保持される。

投入ロジック(Upsert方式)

/execute(自動実行、n8n Scheduleから)実行時、対象フォームごとに:

  1. 手動実行済みフラグ確認 → falseならスキップ(未手動実行のフォームは自動実行しない)
  2. 前回実行日時 + 設定周期から実行時刻判定 → 未到来ならスキップ
  3. 連携キーのaccess token取得(lineworks-user-authGET /token経由)
  4. GET /forms/{formId}/responses呼び出し(cursorページング)
  5. 各回答について、respondent.emailで回答格納先テーブルを検索
    • 一致するレコードあり → 質問マッピングに従って該当列を更新
    • 一致なし → ユーザー照合列にメールアドレスをセットしつつ、質問マッピングに従って新規作成
  6. 添付ファイルがある質問は、LINEWORKS添付ファイルAPI(同access token)でダウンロード→プリザンターItems APIの添付欄へアップロード
  7. 最終自動実行日時を更新

手動実行(管理画面「今すぐ実行」ボタン)は、上記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.jstoHashPayloadと同じprefix規則)。DATE_INPUT/DATETIME_INPUT以外の質問タイプでtargetColumnDate*列が指定された場合はエラーとする(範囲・複数候補を保持できないため)。

エラーハンドリング・通知

/execute、失敗時に適切なHTTPステータス返却。n8n側でエラー分岐→SMTP通知(既存n8n-wf-admin@next-hd.co.jp)。

認証

  • 管理画面(/manage/*): project_lineworks_user_authadminAuth.js/adminView.jsを完全踏襲(Master Key + セッションCookie + 60分限定の一時アクセスキー発行機能、確定)。
  • /execute(n8n→Node.js): 専用実行キー
  • lineworks-user-authGET /keysGET /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ユーザーマスタ連携テーブル、計画中)で対応予定。本アプリは回答者メールアドレスでの直接突合のみ行い、マスタ連携には関与しない
  • 対象フォームの具体的な一覧・作成部署(実装計画時に確定)