ken_nogi/NodeSrv/docs/superpowers/specs/2026-08-07-org-master-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

57 KiB
Raw Permalink Blame History

組織・社員管理データベース構築 設計文書

作成日: 2026-08-07 ステータス: 設計確定LINEWORKS/プリザンター/Keycloak 3システムとも疎通確認済み。SAML SSO連携はテスト環境でログイン完走まで確認済み、Keycloak中間ユーザーDB方式でのプリザンターLoginId非変更運用・氏名連携ともに実証済み。マスタテーブル・退職者検知・グループ命名規則など主要論点は全て確定。実装計画フェーズへ移行可能

1. 目的

LINEWORKS・プリザンター・Keycloakにまたがる社員・組織情報を、プリザンター上の一元マスタテーブルで管理。指示駆動Claude Skillで取得・マージ・反映・同期を実行可能にする。

2. 管理方針

  • 主マスタ: LINEWORKS。社員情報の一次情報源はここのみ
  • LINEWORKS削除者: マスタ物理削除せず、在籍フラグOFFで保持
  • プリザンターのグループ機能: LINEWORKSの組織階層を再現、ユーザー配属先として使用
  • プリザンターの組織機能: マスタテーブルからコントロール
  • 突合キー: メールアドレス自動突合初回のみ。以降はID保持で継続。プリザンターのログインIDは現状特殊ID体系だが、将来メールアドレスへ移行予定運用側で計画中
  • 社員ID: LINEWORKSのuserIdUUIDを採用。実データ確認の結果、employeeNumber人事番号相当フィールドは約25%のユーザーで未設定だったため主キーには使わない。プリザンター側のUserIdとは別管理とし、両者はメールアドレスで紐付ける
  • 所属変更の編集UI: プリザンター標準のテーブル一覧・編集画面をそのまま使用。Node.js側に専用管理画面は作らない

3. 全体アーキテクチャ

新規独立フォルダ既存の1アプリ=1フォルダ規約に準拠、他システムから独立して取り回せる構成

検討して除外した代替案: プリザンターの「ブラウザスクリプト」での実装。CORS制約LINEWORKS/Keycloak APIへブラウザから直接到達できない、認証情報の露出リスク秘密鍵・Client Secretがブラウザ上で閲覧可能になる、大量データ処理時の安定性の問題から却下。プリザンターの「サーバースクリプト」なら技術的制約は回避できるが、コードがプリザンター内に閉じてバージョン管理・レビューがしづらくなるため、Claude SkillNode.js、本リポジトリで管理方式を採用する。

apps/org-master-sync/          ※フォルダ名は仮、確定時に見直し可
  src/
    lib/
      lineworksClient.js       LINEWORKS Directory API。JWT Service Account署名はlineworks-board-syncの既存実装パターンを流用
      pleasanterClient.js      プリザンター REST APIマスタテーブル/ユーザー/組織/グループ)
      keycloakClient.js        Keycloak Admin REST API
    commands/
      fetch-lineworks.js       ① LINEWORKS組織・社員情報取得
      merge-master.js          ② マスタテーブルへマージ・更新
      sync-pleasanter.js       ③ マスタ→プリザンターのユーザー/組織/グループへ反映
      sync-keycloak.js         ④ マスタ→Keycloakへ同期
  .claude/skills/
    org-fetch-lineworks/SKILL.md
    org-merge-master/SKILL.md
    org-sync-pleasanter/SKILL.md
    org-sync-keycloak/SKILL.md
  docs/                        仕様書・検討ログ
  configs/                     サイト情報getsiteのスナップショット保存先git管理対象外、2026-08-08追加
    site-{siteId}_{サイトタイトル}/sitesettings/site-{siteId}_latest.json      最新のサイト設定getsite呼び出しのたびに上書き
    site-{siteId}_{サイトタイトル}/sitesettings/site-{siteId}_{timestamp}.json 更新のたびの履歴バックアップ(削除せず蓄積)
    ※ディレクトリ構造・命名規則は`C:\Users\k.nogi\#GitHub\ken_nogi\ClaudePleasanter`の各プロジェクトの
      `configs/``site-paths.js`のhierarchicalレイアウトと同一2026-08-08、ユーザー指示。
      ただしorg-master-syncは他システムから独立した構成のため、ClaudePleasanter側のコードは
      requireせず`src/lib/siteConfigStore.js`に同じ構造を独自実装している
  .env.example
  README.md

データフロー

LINEWORKS Directory API
   │ ① fetch
   ▼
プリザンター"社員・組織マスタ管理テーブル"  ← SSOT
   │ ② mergeuserId=社員IDで突合、初回はメール自動突合
   │                                              │ ③ sync
   │                                              ▼
   │                               プリザンター ユーザー/組織/グループ
   │ ④ sync
   ▼
Keycloaknexthdレルムユーザー/グループ

4. 実行単位

4コマンドは独立実行。各々Claude Skillでラップし、「LINEWORKSから取得して」等の自然文指示で個別起動可能にする。一括パイプライン実行は今回のスコープ外将来必要になれば別Skillとして追加

5. マスタテーブル カラム確定・LINEWORKS準拠版、タブ構成

方針転換: マスタテーブルの項目定義はLINEWORKSのユーザー情報に準拠する。プリザンター側に対応項目が無くても、LINEWORKSから取得できる情報は原則すべて保管する他システムへの汎用性を考慮。将来Keycloak以外のシステム連携でも同じマスタを参照元にできるようにするため。加えて、プリザンターのユーザー登録API/api/users/create/api/users/updateが受け付ける全項目もマスタで管理し、③sync-pleasanterが過不足なくプリザンターユーザーを作成・更新できるようにする。

LINEWORKS Directory API /usersの実レスポンス(data/lineworks-user-sample-raw.jsondata/lineworks-users-full.jsonで351人分を実際に取得・確認済みを踏まえて確定。編集画面は3タブ構成とし、情報の出自ごとに配置する。

5.0 タブ構成

タブ 内容
1. 基本情報(既定表示) 主キー・氏名(分割/カナ/英字)・生年月日・監査用日時・備考
2. プリザンター プリザンターのユーザー登録APIが必要とする全項目
3. LINEWORKS 上記2タブで拾いきれないLINEWORKS固有情報

5.1 タブ1: 基本情報

No 列名 内容 割当カラム 補足
1 社員ID userIdUUID ClassA 主キー。プリザンターGlobalIdへ同じ値を書き込む(③)
2 メールアドレス email ClassB 突合キー初回のみ。ローカル部一致で94%的中確認済み)
3 userName.lastName Class001
4 userName.firstName Class002
5 姓(カナ) userName.phoneticLastName Class003
6 名(カナ) userName.phoneticFirstName Class004
7 姓(英字) i18nNames[]language: "en_US"lastName Class005 英語表記が未設定の社員は空。複数言語登録があってもen_US優先で1件のみ保持
8 名(英字) i18nNames[]language: "en_US"firstName Class006 同上
9 生年月日 birthday Date007 プリザンターユーザー登録の「生年月日」項目とも共用LINEWORKS由来の値をそのまま使い、二重管理しない。番号は姓名等Class001〜006の続き番号種類を問わず連番、2026-08-08ルール
10 備考 自由記述 Body(標準項目)
11 最終LINEWORKS同期日時 ②実行時に更新 DateA 監査・差分検出用7章。実機ラベルは「LW同期日時」に短縮
12 プリザンター登録日時 ③初回作成時に更新 DateB 実機ラベルは「PL登録日時」に短縮
13 プリザンターのユーザー情報更新日時 ③更新時に更新 DateC 実機ラベルは「PL更新日時」に短縮
14 LINEWORKS有無 ②実行時に判定(二択: 有/無) ClassZ LINEWORKS取得結果に存在すれば「有」、プリザンター専用ユーザーLINEWORKSに存在しないなら「無」。2026-08-08追加ユーザー指示
15 プリザンター有無 ②実行時に判定(二択: 有/無) ClassY プリザンター実ユーザーとして突合できれば「有」、できなければ「無」。2026-08-08追加ユーザー指示

氏名の結合表示(「姓 名」形式は独立列を持たず、タブ2「名前」Class013がその役割を兼ねる。Keycloak同期時のカスタム属性fullName9章の氏名連携の恒久対応も、④sync-keycloakがClass013の値をそのまま使う。

ClassZ/ClassYのChoicesText設定について: この2項目のみmasterFields.jsbuildHashUpdate(トップレベルClassHash経由でのラベル一括設定)の対象外(hash: "columns-only")とし、scripts/add-existence-flag-fields.jsで個別にSiteSettings.ColumnsChoicesText: "有\n無"を設定した。理由は5.7節「ハマった点」参照。

5.2 タブ2: プリザンターユーザー管理画面の全項目、2026-08-08確定版

公式マニュアル(JSONデータレイアウトUser)の生データを確認したところ、ユーザー管理画面(スクリーンショットで確認)に表示される項目の一部(「管理者」ドロップダウン・テーマ・各種「許可」チェックボックス・秘密鍵有効・ログイン有効期限・無効化までの日数)に対応するプロパティが同ページには見当たらなかった。一度これらを除外する案を検討したが、方針を変更し、ユーザー管理画面に表示される項目はすべてマスタで管理することに確定。その後/api/users/getの実機レスポンス公式マニュアルに記載の無いプロパティも含む全体像を確認し、「対応プロパティ未確認」だった項目の実名がほぼすべて判明した下表参照、2026-08-08

番号ルール2026-08-08確定: プリザンター項目タブ2は項目の種類Class/Date/Description/Checkを問わず、011から通し番号を振る。真偽値項目はCheckA〜のような文字割当ではなくCheck020のように番号付きで統一する。

No 列名 割当カラム 備考
1 ユーザIDプリザンター内部UserId Class011 公式プロパティUserId
2 ログインID Class012 LoginId
3 名前(プリザンター表示名、結合) Class013 Name
4 ユーザコード Class014 UserCode
5 性別 Class015 Gender。LINEWORKSに情報源なし、手動設定。画面上は現在非表示項目だが、将来表示される可能性があるためマスタでは維持
6 言語(既定: Japanese Class016 Language
7 タイムゾーン(既定: Japanese Standard Time Class017 TimeZone
8 組織 Class018 [[Depts]]選択式5.5節)。実機ブラウザ確認の結果、格納値は「組織ID」プリザンター組織テーブルのDeptId、数値と判明。公式User APIのDeptCode文字列とは別物の可能性が高く、③sync-pleasanter実装時にDeptId→DeptCode変換の要否を検証する
9 管理者(ユーザー選択) Class019 [[Users*]]選択式5.5節)。実機確認済み、他ユーザーのUserId数値を指定する形式。実プロパティ名Managerと判明(/api/users/get実機レスポンスで確認、2026-08-08
10 テナント管理者 Check020 TenantManager真偽値。上記「9. 管理者」ドロップダウンとは別物
11 テーマ Class021 Theme
12 説明 Description022 Body
13 パスワード有効期限 Class023 PasswordExpirationTime(テナント管理者のみ取得可能)
14 サイトトップへの作成を許可 Check024 AllowCreationAtTopSite
15 グループの管理を許可 Check025 AllowGroupAdministration
16 グループの作成を許可 Check026 AllowGroupCreation
17 APIを許可 Check027 AllowApi
18 サイトトップからの移動を許可 Check028 AllowMovingFromTopSite
19 無効 Check029 Disabled(テナント管理者のみ取得可能)
20 ロック Check030 Lockoutプロパティ名は"Lockout"。テナント管理者のみ取得可能)
21 秘密鍵有効 Check031 SecretKey(文字列。空文字なら無効、値があれば有効とみなす)
22 ログイン有効期限 Class032 LoginExpirationLimit
23 無効化までの日数 Class033 LoginExpirationPeriod(数値)
24 パスワード Class034 データレイアウト表には無いが、import-prod-to-test.jsで実際にPasswordパラメータでの作成に成功済み書き込み専用のため一覧・取得APIレスポンスから省かれる
25 所属グループ(複数選択) Class035 標準プロパティではなく[[Groups*]]選択式5.5節)

「8. 組織」はLINEWORKSとは連動しない独立管理項目一覧画面で人手管理、③はマスタ→プリザンターへの一方向反映のみ。「25. 所属グループ」はLINEWORKSの全所属organizations[].orgUnits[]、primary含む全部に対応するプリザンターGroupIdの集合をそのまま再現し、①②で自動更新する値の形式・マージ方式は5.5節参照)。

②merge-masterによるタブ2ミラーリング2026-08-08方針転換・実装済み: 当初「タブ2はLINEWORKS由来ではないため②はgroups以外書き込まない」としていたが、ユーザー指摘により方針転換。②merge-master実行時、LINEWORKS取得データに加えて/api/users/getView:{ApiGetMailAddresses:true}でプリザンターの実ユーザーアカウント一覧も取得し、既にプリザンターにアカウントが存在する社員については上表No.1〜23Class011Check031Class032Class033。No.24パスワードは取得不可のため対象外をタブ2へミラーリングする読み取りのみ、プリザンター側への書き込みは行わない。プリザンター側への書き込みは従来どおり③sync-pleasanterの担当。アカウント未作成の社員はタブ2が空のまま。突合キー・重複時の扱いは7章「プリザンター実ユーザーとの突合」参照。

編集画面での表示順(EditorColumnHashはユーザー側で手動調整済み。項目の割当カラム番号自体は上表の通り変わらないが、表示順は「24. パスワード」が「12. 説明」の直後「13. パスワード有効期限」より前に配置されている2026-08-08時点の実機、レビュー時の混乱防止のため記録

LastName/FirstName(公式レイアウトに存在、姓名を個別に持つ)・BirthdayGlobalIdについて: これらはタブ1のClass001Class002(姓・名)・Date007(生年月日)・ClassA社員IDと実質的に同じ情報のため、タブ2に別枠を設けず、③sync-pleasanter実装時にタブ1の値をそのままAPIのLastName/FirstName/Birthday/GlobalIdへマッピングする(LastNameClass002名、FirstNameClass001姓、という対応になる可能性が高い点に注意。公式マニュアルの日本語ラベルとプロパティ名が入れ替わっているように見えるため、③実装時に実機で要検証)。

5.3 タブ3: LINEWORKS固有情報

タブ1・タブ2で既に置き場所のある項目氏名各種・メール・生年月日等は重複させず、ここには残りのLINEWORKS固有情報のみを置く。役職・職級・利用権限タイプはLINEWORKS上で別々の分類/directory/positions/directory/levels/directory/user-types)のため独立した列として保持し、③のロジックで所属グループと合わせて対応するプリザンターグループへユーザーを所属させる(それぞれ同名のグループを事前に用意する運用)。

番号ルール2026-08-08確定: LINEWORKS固有項目タブ3も種類を問わず051から通し番号を振るタブ2と同じ考え方

No 列名 内容 割当カラム
1 私用メールアドレス privateEmail Class051
2 ニックネーム nickName Class052
3 社員番号 employeeNumber Class053約25%のユーザーで未設定のため主キーには不採用、2章
4 電話番号 telephone Class054
5 携帯電話番号 cellPhone Class055
6 勤務地 location Class056
7 役職 positionNameprimary所属、8種 Class057選択式、5.5節参照)
8 職級 levelName6種: 役員/正社員/嘱託・契約/パート社員/実習生/その他) Class058選択式、5.5節参照)
9 利用権限タイプ userTypeName4種: 管理部門/非住宅事業/有期雇用/ダミーアカウント) Class059選択式、5.5節参照)
10 入社日 hiredDate Date060
11 エイリアスメール(複数、;区切り) aliasEmails[] Description061
12 LINEWORKS在籍フラグ 在籍中/削除済み Check062ON=在籍中。物理削除しない。退職者は取得結果から消えるため前回リストとの差分検知が必要、7章
13 休職中フラグ leaveOfAbsence.isLeaveOfAbsence Check063
14 停止中フラグ isSuspended Check064(在籍はしているが一時的にアカウント停止中。在籍フラグとは別軸)
15 LINEWORKS管理者フラグ isAdministrator Check065タブ2「10. テナント管理者」とは別概念、重複ではない)

5.4 取得可能だが今回は列を割り当てなかった項目(実データ確認済み)

「LINEWORKSから取れる情報はすべて取って保管」の方針の例外として、以下は実データで内容を確認した上で意図的に除外した。理由とともに明記する。

フィールド 除外理由
employmentTypeId/employmentTypeName 351人全員でuserTypeId/userTypeNameと完全に同一の値を確認別名の同一フィールドと判断。列22利用権限タイプと重複するため不採用
isDeleted /usersの一覧結果に含まれる時点で常にfalse削除されたユーザーはそもそも結果に含まれない、7章の退職者検知方式と整合。値として意味を持たないため不採用
isAwaitingisPending 招待中・承認待ちのステータス。実データ上ほぼ全員falseで、現行の運用(招待フロー管理)では使う場面が無いため不採用。将来必要になれば追加可
customFieldsrelationscustomProperties 351人全員で空配列/null。この組織のLINEWORKSテナントでは未使用の機能のため不採用
localetimeZone 確認した範囲では全員ja_JP/Asia/Tokyo固定。実質定数のため不採用
domainIduserExternalKeyorganizations[].organizationName LINEWORKSの内部ID/定数(organizationNameは全員"next"固定)。社員ごとの差分情報を持たないため不採用
taskmessenger 実データ上ほぼ全員未設定。プロフィールの補助項目で業務上の必要性が薄いため不採用

5.5 選択式項目の設計(組織・所属グループ・役職・職級・利用権限タイプ)

自由テキストではなく、プリザンター標準の選択肢機構を使い、選択式(ドロップダウン/複数選択)にする。SiteSettings.Columns配列の該当ColumnNameエントリにChoicesTextを設定することで実現する(getsite実データで構造を確認済み、下記「ハマった点」参照)。

組織(Class018)・管理者(Class019)・所属グループ(Class035: プリザンター標準のシステム参照キーワードを使う。実機(ユーザー側の手動設定)で最終確認したChoicesTextとプロパティは以下の通り2026-08-08時点

項目 ChoicesText 補足プロパティ 格納値(ブラウザ実機確認済み)
組織 [[Depts]]アスタリスク無し、単一選択) UseSearch: true プリザンター組織テーブルの組織IDDeptId、数値
管理者(ユーザー選択) [[Users*]] UseSearch: true, SearchType: "PartialMatch"。「9. 管理者」ドロップダウンの正体はUsersシステム参照と判明 他ユーザーのUserId数値
所属グループ [[Groups*]] UseSearch: true, MultipleSelections: true, SearchType: "ExactMatch"複数選択の実体はChoicesText*ではなくMultipleSelections: trueプロパティ(実機確認済み) GroupIdのカンマ区切り文字列例: "1,4"

組織IDClass018をプリザンターのユーザー登録・更新API/api/users/update等)へそのまま渡してよいか(DeptIdとして渡せるのか、DeptCodeへの変換が必要かは、③sync-pleasanter実装時に実機で検証するユーザー確認済み、2026-08-08

所属グループは自由テキスト(旧Description002案)からClass型・複数選択に変更(Class035)。選択される値はプリザンターの実際のGroupIdをカンマ区切りにした文字列(実機確認済み、例: "1,4";ではなく,。LINEWORKSのグループ名テキストではない。

分類グループの命名・自動作成get-or-create、2026-08-08確定: LINEWORKS初期連携時点では、7章で定めた分類プレフィックス付きグループ組織:{orgUnitName}役職:{positionName}職級:{levelName}利用権限タイプ:{userTypeName}はプリザンター側にまだ1つも存在しない実データ確認済み。テスト環境の既存60グループはすべて業務用グループで、分類プレフィックス付きのものは無い。そのため②merge-masterは以下の手順で解決する。

  1. 事前に/api/groups/getで全グループを1回取得し、GroupName → GroupIdのキャッシュを作る
  2. 社員ごとに必要な分類グループ名4分類、複数所属があれば「組織:」は複数になりうる)を組み立てる
  3. キャッシュに無ければ/api/groups/createで新規作成し、キャッシュへ追加(同一実行内での重複作成を防ぐ)
  4. 対象社員の既存Class035あればとマージ後のGroupIdの和集合をカンマ区切りで書き込む

/api/groups/getのレスポンスGroupMembers"User,{UserId},{Flag}""Dept,{DeptId},{Flag}"形式の配列(公式マニュアルで確認済み)。グループの新規メンバー登録はGroupMembersの更新で行う想定だが、Phase 1②merge-masterのスコープでは社員側のClass035(マスタ内の選択)のみを更新し、グループ側のGroupMembers実際のプリザンターユーザーのグループ所属は③sync-pleasanterが担当するマスタはあくまで「あるべき状態」を持つ台帳であり、実際のプリザンターユーザーへの反映は③の責務という原則、design全体を通じて一貫追記は必ず既存の選択IDとの和集合マージで行い、LINEWORKS由来でない手動選択済みのGroupIdを消さない他システム・手動運用で追加されたグループ選択を保持するため。LINEWORKS側で外れたグループの選択解除除去は今回のスコープ外とし、将来の課題とする。

役職(Class057)・職級(Class058)・利用権限タイプ(Class059: 外部の選択肢マスタテーブル(それぞれ独立したプリザンターサイト、ユーザー側で作成済み)への参照リンクとして選択式にする。

項目 参照先SiteId.env 現在の値
役職 PLEASANTER_POSITION_MASTER_SITE_ID 3
職級 PLEASANTER_LEVEL_MASTER_SITE_ID 5
利用権限タイプ PLEASANTER_USERTYPE_MASTER_SITE_ID 6

ChoicesTextの書式実機で動作確認済み:

[{"TableName":"<参照先SiteId>","View":{"ColumnSorterHash":{"ClassA":"asc"}}}]

ClassA昇順でソート(ClassAは下記マスタの表示順、3桁ゼロ埋め

3つの選択肢マスタテーブルの構造3サイトとも同一構造。役職マスタ=SiteId 3で先行カスタマイズし、職級=5・利用権限タイプ=6へ複製済み:

  • 標準の「記録テーブル」(ReferenceType: Results)。カスタム列はClassAのみ
  • ClassAラベル「No」: 表示順を3桁ゼロ埋めで指定例: 001, 002...。LINEWORKSのdisplayOrder値の昇順に対応
  • Title: 選択肢として表示される値そのもの(例:「取締役」「役員」「管理部門」)
  • Status列を追加し、選択肢の有効/無効を管理する: ChoicesText"100,有効,有効,status-blue\n910,無効,無効,status-rejected"LINEWORKSから役職・職級・利用権限タイプを再取得し、既存の値が結果から消えていた場合、レコードを削除せずStatus=910無効に更新する社員マスタ本体の在籍フラグ方式、7章と同じ思想。選択肢としては非表示/選択不可になるが、既存の参照整合性は壊さない)
  • 初期データ投入済み: 役職8件ClassA=001008・職級6件001006・利用権限タイプ4件001004)。全件Status=100(有効)

実データ調査で判明した3分類の値一覧生値・displayOrder付き: apps/org-master-sync/data/lineworks_positions.csvlineworks_levels.csvlineworks_usertypes.csv

5.6 プリザンター側の実装先サイト

マスタテーブルは新規サイト作成ではなく、既存の空サイトSiteId=1、現在のTitleは「記録テーブル1」、ReferenceTypeResults、ユーザー側で用意済み)をカスタマイズして使う。PLEASANTER_MASTER_SITE_IDとして.envに設定済み。

getsite/api/items/1/getsite)で現状構造を実際に取得し確認済み。フィールドの型・ラベルはData直下のClassHash/NumHash/DateHash/DescriptionHash/CheckHash(現状すべて{}、未設定)で管理される方式({"ClassA": "社員ID"}のようにフィールドキー→ラベルのハッシュ。選択式項目5.5節)のChoicesTextは別途Data.SiteSettings.Columns配列で設定する({"ColumnName":"Class057","ChoicesText":"..."})。上記表の「割当カラム」はこの方式に基づく。

反映はupdatesite/api/items/1/updatesite、Mode:full相当で、現状のDataTitle/ReferenceType/ParentId/InheritPermission/Permissions等)を保持したまま、Title・5つのHash・SiteSettings.Columns選択式5項目分を書き換える。updatesitesettings部分更新は環境によって反映されないことが別プロジェクトで確認済みのため使わない。既存レコードは0件新規サイトのため、データ移行は不要。

5.7 ハマった点サイト構築・API関連

  1. SiteSettings.ColumnsのChoicesText等はUI手動設定→getsiteで実際のJSON構造を確認→updatesiteで他サイトへ複製、という手順が確実: [[Depts*]][[Groups*]]・外部サイト参照リンク(TableName/ColumnSorterHash)・Status列のChoicesText書式は、いずれもユーザーがUIで先行構築した実サイトをgetsiteで読み取って確定した推測のJSON構造を先に書かず、実機確認を徹底
  2. /api/items/{SiteId}/get(複数レコード取得)・/api/items/{ResultId}/get(単体取得)・/api/items/{ResultId}/deleteがこの環境では404: getsitecreateupdatesite{ResultId}/updateは正常動作するが、取得系・削除系のみアプリ側の404ページが返るnginxではなくPleasanter自体のエラーページ。原因未特定APIキーの権限設定で「取得」「削除」が許可されていない可能性。②merge-master等が既存レコード一覧を取得する処理Task 4・退職者検知で要再検証・代替手段の検討が必要
  3. レコード単体の更新URLは/api/items/{ResultId}/updateResultIdのみ、SiteIdは不要): /api/items/{SiteId}/{ResultId}/updateという3階層パスはnginxレベルで404になり、アプリにすら到達しない。SiteIdを含めずResultId単体を指定する2階層パスが正しい
  4. 【解決済み】この環境のDBPostgreSQLには当初、3桁連番の拡張項目カラムClass001〜、Date001〜、Description001〜、Attachments001〜等)が物理的に存在しなかった: ClassAClassZ等の1文字26個カラムは実在するが、Class001のような拡張項目をColumns/EditorColumnHash/GridColumnsのどこか一箇所にでも参照すると、一覧のソート・レコード編集画面表示・一括削除など該当箇所の操作すべてがPostgresException: 42703: column "Class001" of relation "Results" does not existで例外落ちしていたシステムログで実際のスタックトレース・SQLエラーを確認済み。原因はEnterprise Edition機能「拡張DB書き込み」が未有効だったこと。ユーザー側でサーバー上のImplem.CodeDefiner.dll _rdsDBスキーマ拡張コマンドを実行して解消実行時に一度Postgresの権限エラー42501: permission denied for schemaが発生したが、スキーマの所有権限を修正後に再実行し成功)。2026-08-08、Class001Class059Date001Date051Description001Description051まで実際にレコード作成・取得できることを確認済み
  5. 上記4の解消により、items/get(一覧・単体取得)・items/{id}/deleteのAPIも合わせて復旧した: これらの404は権限起因ではなく、拡張カラム欠如によるSQLエラーがPleasanter側で404相当のエラーページに変換されて返っていたことが判明4の対応と同時に解消。誤って作成し削除不能だった旧レコードResultId=2もこのタイミングでUI・APIとも削除に成功
  6. 役職・職級・利用権限タイプ([[SiteId]]参照リンク型ChoicesTextの実際の格納値を確認: 実データ相当のテストレコード作成で検証した結果、Class057等には選択肢マスタのTitle文字列がそのまま格納されるIDではなく文字列。②merge-masterのuserToFieldsが生成するpositionName等の文字列をそのまま書き込む設計で問題ないことを確認済み(Class018[[Depts*]]Class035[[Groups*]]は未検証、実際のDept/GroupIdでの検証が必要
  7. 画面のチェックボックス項目 ≠ 公式API JSONデータレイアウト: ユーザー管理画面のスクリーンショットには「管理者」(ユーザー選択)・「テーマ」・各種「許可」チェックボックス・「秘密鍵有効」・「ログイン有効期限」・「無効化までの日数」等が表示されるが、公式マニュアル「JSONデータレイアウトUser」の生データMarkdown表にはこれらに対応するプロパティが存在しない。UI専用の設定、または別の権限体系グループ単位の権限等で管理されている可能性がある。一度これらをマスタから除外する案としたが、画面の全項目を管理する方針に変更2026-08-08。API経由での読み書き可否は③sync-pleasanter構築時に個別検証し、不可と判明した項目もマスタ自体には残す5.2節)
  8. 番号体系を「種類問わず連番」に統一2026-08-08: 当初はハッシュ種別Class/Date/Description/Checkごとに独立採番していたが、CheckA〜のような文字割当とClass011〜のような番号割当が混在し分かりにくくなったため、タブごとに種類を問わない通し番号タブ2は011〜、タブ3は051〜、タブ1の姓名・個人情報系は001〜)に統一。真偽値もCheck020のように番号付きで統一した。実機(Check020Date007Description022等)で作成・取得とも動作確認済み
  9. 未使用の単独レターClassClassCClassZは、プリザンター管理画面のUIで「項目追加」してからでないとAPIから使えない: 新規追加したClassZ/ClassYupdatesiteのトップレベルClassHashに含めて送ると404になった既存のClassA単体を再送信するだけでも同様に404になり、一見「サイト全体が壊れた」ように見えたが、実際は「未使用フィールドをそのAPIパスから初めて有効化しようとした」ことが原因だった。ユーザーがプリザンター管理画面のUIで項目を追加ラベル等の詳細は未設定のまました後は404が解消した
  10. SiteSettings.Columns(列の詳細設定=ChoicesText等はAPIリクエストのトップレベルではなくSiteSettingsオブジェクト配下にネストして送る必要がある: {ApiVersion, ApiKey, ..., Columns: [...]}のようにトップレベルへColumnsを置いて送信すると200 OKが返るが実際には一切保存されない(サイレント失敗)。正しくは公式マニュアルapi-site-updateの例のとおり{..., SiteSettings: {Version, ReferenceType, GridColumns, EditorColumnHash, Columns, ...}}という形。トップレベルのClassHash/DateHash等(フィールドラベル一括設定用)とは全く別の更新経路であることに注意。ClassZ/ClassYのChoicesText設定はこの正しい形式で送って初めて反映されたscripts/add-existence-flag-fields.js参照)

6. 認証・接続方針

3システムとも、本プロジェクト専用の新規認証情報を発行して使用する。既存の認証情報board-sync用LINEWORKS Service Account、app-gateway用Keycloakクライアント等とは混在させない。

システム 現状 必要な準備
LINEWORKS 疎通確認済み。専用Service Accountboard用v4s3mとは別のwh4k8アカウント)とスコープuser.read orgunit.read group.read/users/orgunitsからの実データ取得を確認済み なし(.env設定済み、秘密鍵はkeys/lineworks_private.keyに配置済み)
プリザンター 疎通確認済み。本番(nextoffice.next-hd.co.jp、587ユーザー)と別に、書き込みテスト専用環境(nextoffice2.next-hd.net、2ユーザーのみのクリーン環境)をユーザー側で用意済み。.envは現在テスト環境を指す なし
Keycloak 疎通確認済みnexthdレルムに専用クライアントorg-master-syncを新規作成Client Credentials方式、realm-managementロールのmanage-users/query-groups付与)。既存app-gatewayStandard flow専用とは別クライアントで、既存ログインフローに影響なし なし(.env設定済み)

.env等の機密情報はAUTH_SECRETS.mdと同様の扱いgitignore・チャット非表示とする。

7. マージ・同期の詳細仕様(確定)

退職者検知・扱い

  • 検知方法: マスタテーブル自体を「前回リスト」として使う。②merge-master.js実行時、マスタに在籍中在籍フラグONで存在する社員IDのうち、今回のLINEWORKS取得結果に含まれないものを退職と判定し、在籍フラグCheck062をOFFにする。別途の履歴ファイル管理は不要
  • 【修正済み・2026-08-09】退職検知時にClassZLINEWORKS有無が更新されない不整合があった: 当初retirements処理はCheck062のみをfalseに更新しており、ClassZは"有"のまま取り残されていた④sync-keycloak設計時に発覚Check062と同時にClassZ"無"へ更新するよう修正済み(planMergeretirementsマッピング)
  • プリザンター・Keycloak双方の扱い: 両方とも無効化のみ行い、削除はしない。プリザンター(③)はDisabled: true本番で226件の実績あり、既存運用と整合、Keycloakenabled: false

プリザンター実ユーザーとの突合②merge-master、2026-08-08追加

  • 突合キー: メールアドレスのローカル部(@より前)一致。完全一致ではない。実データ確認の結果、プリザンター実ユーザー592件中332件がnexthd.jp(ハイフン無し)ドメイン、next-hd.co.jpLINEWORKSと同じドメインはわずか15件のみだったため、完全一致では大半が突合できなかった。管理方針2章の「将来メールアドレスへ移行予定」を踏まえ、ローカル部一致に緩めることで325/351件が突合できることを確認済み
  • 重複時の扱いget-or-confirm方式: 同一ローカル部に複数のプリザンターUserIdが紐づく場合本番移行時のレガシーアカウント等が原因、実データで3件確認、自動判定せずplan.needsReviewに積んで実行時にコンソール警告する。採用するUserIdをdata/pleasanter-user-email-overrides.json{"ローカル部": UserId}形式、gitignore対象に人手で1回追記すれば、以降の実行では自動的にその選択が使われる
  • 同一ユーザーが複数メールアドレスを持つケース: 同じUserIdが複数のメールアドレス同ローカル部・別ドメイン等を持つ場合は重複ではないため、needsReviewの対象にしない(実データで発覚したバグ、修正済み)
  • ミラーリングの向き: 一方向プリザンター→マスタのタブ2のみ。マスタからプリザンターへの書き込みは③sync-pleasanterの担当
  • プリザンターに登録されている全ユーザーは漏れなくマスタへ登録必須2026-08-08、ユーザー指摘・2度の方針拡張:
    1. まずLINEWORKSに存在しないプリザンター専用ユーザー退職済み・無効化済み・LINEWORKS管理外等もマスタへ登録するよう拡張。タブ1基本情報・タブ3LINEWORKS固有情報はLINEWORKS由来情報が無いため空のまま、タブ2プリザンター管理項目のみ埋める。Check062LINEWORKS在籍フラグfalse固定
    2. さらに、メールアドレスを一切持たないプリザンターユーザーテスト・退職者・システムアカウント等、実データで240件確認もメールローカル部突合では拾えず漏れることが判明。突合の主軸をプリザンターUserIdClass011)に変更し、pleasanterUsers全件を直接ループする方式へ修正
    • 実データ確認済み最終件数: 351件LINEWORKS在籍268件プリザンター専用、重複解決分含む619件プリザンター実ユーザー592件全員がマスタのどこかに反映されていることを確認済み
  • 【ハマった点・修正済み】既存のLINEWORKS社員レコードを誤って上書きするバグ: プリザンター専用ユーザーの突合に使うbyPleasanterUserId・メールローカル部マップを、LINEWORKSループの今回処理対象のLINEWORKS社員を除外せず構築していたため、同一ローカル部を持つ「採用されなかった方」の重複プリザンターアカウントを処理する際、既にLINEWORKS社員として正しく更新済みのマスタレコードを誤ってヒットさせ、Check062をfalseで上書きしてしまう事故が発生実データで発覚: 野木健一郎・高橋光晴の在籍フラグが誤ってfalseになった。修正: 両マップの構築をLINEWORKSループのに移し、!seenEmployeeIds.has(i.ClassA)今回LINEWORKS社員として処理済みでないことでフィルタしてから使うよう変更
  • 【ハマった点・修正済み】プリザンター専用ユーザー同士がメールローカル部突合で互いの既存レコードを奪い合うバグ: 同一メールローカル部に3つ以上のプリザンターUserIdが紐づくケースが実データで見つかった例: kenichiro.nogiに593/36/266/493の4件。プリザンター専用ループの突合に「Class011優先、無ければメールローカル部」というフォールバックを使っていたため、あるUserIdが先にローカル部で既存レコードを掴むと、後から処理される別のUserIdも同じローカル部一致で同じレコードを掴んでしまい、実行順序によって1件だけしかマスタに定着しない不安定な事故が発生した592件のはずが617件しかなく1件不足していた形で発覚。修正: プリザンター専用ループの突合を**Class011一致のみ**に限定し、メールローカル部フォールバックを廃止

役職・職級・利用権限タイプの格納値②merge-master、2026-08-08追加・訂正

5.3節のClass057/Class058/Class059は、当初LINEWORKSのpositionName/levelName/userTypeName文字列をそのまま格納する設計だったが、格納すべき値は対応する外部選択肢マスタSiteId=3/5/6、5.5節)の該当レコードのResultIdであると訂正2026-08-08、ユーザー指摘。②merge-masterapplyMergeが実行時に、3つの外部マスタからTitle→ResultIdのキャッシュを構築し、Class035所属グループと同じget-or-createパターンで解決する。対応するTitleが外部マスタに存在しない場合は新規レコードを自動作成する表示順ClassAは空のまま、手動調整が必要)。実データ確認済み: 役職8件・職級6件・利用権限タイプ4件がLINEWORKSの全件と完全一致しており、現状は既存レコードの参照のみで新規作成は発生しない

グループ命名規則・自動作成

  • 命名規則: 分類プレフィックス付きで4分類を統一。組織:{orgUnitName}役職:{positionName}職級:{levelName}利用権限タイプ:{userTypeName}
  • 事前作成方法: 手動での事前準備は不要。③④の同期処理が、存在しないグループを自動作成するプリザンターCreate Group API・Keycloak Admin APIとも検証済み。LINEWORKS側で新しい役職・組織が追加されても自動追従する

実行トリガーは指示駆動に確定: 定期実行cron等は導入せず、人が「同期して」等と依頼した時のみ①〜④を実行する運用とする。スケジューリング機構・エラー通知の追加検討は不要。

8. Keycloak同期④sync-keycloak.js詳細設計

ユーザー同期

  • Upsert方式。突合キーはKeycloakユーザーのカスタム属性lineworksUserId社員ID=LINEWORKSのuserIdをそのまま格納。プリザンターのGlobalIdと同じ役割)
  • 初回のみemailで突合し、見つかったユーザーにlineworksUserId属性を書き込む(プリザンターの初回メール自動突合と同じパターン)。以降はlineworksUserIdで検索
  • Keycloak側での新規ユーザー作成はしない前提の確認。LINEWORKS→Keycloakの既存SSO連携では初回ログイン時にユーザーが自動作成されるが、④はそのタイミングに依存しない。運用としては「LINEWORKSログイン時にはKeycloakユーザーが存在する」状態を、管理本部が任意のタイミングで④を実行することで事前に作っておく
  • 書き込む属性: fullName氏名、9章の氏名連携恒久対応pleasanterLoginIdプリザンター用LoginId、9章のNameID Mapper方式で実証済みlineworksUserId(突合キー自身)
  • 退職者はenabled: false7章

グループ同期

  • LINEWORKSの「所属グループ複数、organizations[].orgUnits[]全所属」「役職」「職級」「利用権限タイプ」の4分類5章に対応するKeycloakグループを、命名規則7章に従って作成し、ユーザーを所属させる
  • プリザンターだけでなくKeycloak側にも同じグループ構成を持たせる。将来の他システム連携XWiki等でSAML/OIDCのgroupクレーム経由の部署単位アクセス制御を見据えた判断
  • グループは同期処理が自動作成7章

9. LINEWORKS → Keycloak → プリザンター SAML SSO連携

「全社システム統合・AI活用基盤構想」の本題README「未着手タスク」記載。プリザンターのテスト環境nextoffice2.next-hd.net)で設定スキームを確立し、疎通確認済み。設定ファイル一式はapps/org-master-sync/pleasanter-saml/

LINEWORKS(IdP)は既存のKeycloak連携nexthdレルム、Identity Providers > lineworksをそのまま使用、変更不要。今回追加したのはKeycloak側の新規SAML ClientプリザンターをSPとして登録と、プリザンター側Authentication.jsonのSAML設定。

確立した設定

Keycloak側nexthdレルム、Clients

項目
Client type SAML
Client ID https://nextoffice2.next-hd.net/pleasanter/Saml2
Valid redirect URIs https://nextoffice2.next-hd.net/pleasanter/Saml2/Acs正確なACS URLに確定。下記「ハマった点4」参照
Name ID format email
Keys > Client signature required OFF
Advanced > Authentication flow overrides > Browser Flow browser-idp-only(下記参照)

Keycloak側 汎用Browser Flow「browser-idp-only」(他システム、例: XWikiでも使い回す想定で汎用名にした

browserフローを複製して作成。既定のbrowserフローとの差分のみ:

  • サブフローbrowser-idp-only formsUsername Password Formを含む: AlternativeDisabled
  • Identity Provider Redirectorの設定(歯車アイコン): Aliasに任意の名前、**Default Identity Providerlineworks**を設定

これにより、このFlowを割り当てたクライアントはログイン時に自動的にLINE WORKSへリダイレクトされ、username/passwordフォームやIdP選択画面を経由しない。他のクライアントapp-gatewayには影響しないFlowはクライアント単位のoverrideのため

プリザンター側(Authentication.json、要点)

項目 補足
Provider "SAML"
SPOptions.EntityId https://nextoffice2.next-hd.net/pleasanter/Saml2
SPOptions.ReturnUrl https://nextoffice2.next-hd.net/pleasanter/Users/SamlLogin
SPOptions.AuthenticateRequestSigningBehavior "Never"
IdentityProviders[0].EntityId https://auth91.next-hd.net/realms/nexthd レルムのentityID。メタデータ取得URLとは別物
IdentityProviders[0].MetadataLocation https://auth91.next-hd.net/realms/nexthd/protocol/saml/descriptor
IdentityProviders[0].LoadMetadata true
IdentityProviders[0].SigningCertificate セクション自体を削除 残すと起動不能(下記参照)

ハマった点(トラブルシューティング知見)

  1. EntityIdにメタデータURLをそのまま入れると起動時500エラー: Unexpected entity id ... found when loading metadata。Sustainsys.Saml2はEntityId(=IdPのentityID)とMetadataLocation(=メタデータ取得先URL)を別々に指定する必要がある
  2. SigningCertificateセクションを未設定値(FindValue: null)のまま残すと起動不能: X509Certificate2Collection.FindArgumentNullExceptionを投げる。LoadMetadata: trueでも証明書ストア参照処理は無条件で実行される実装のため、使わないならセクションごと削除する
  3. Keycloak Clientをレルムを間違えて作成master)するとInvalid Request: SAML ClientはIdPとして機能させたいレルムnexthd)に作成する必要がある
  4. Valid redirect URIsの完全一致指定でInvalid redirect uri: 実際にプリザンターが送信するAssertionConsumerServiceURLは{SPOptions.EntityId}/Acsという固定パターンSustainsys.Saml2の仕様。今回の場合https://nextoffice2.next-hd.net/pleasanter/Saml2/Acsで、SPOptions.ReturnUrl/Users/SamlLoginとは別物。実際に送信されたSAMLRequestHTTP-Redirect binding、Base64+deflate圧縮をデコードして特定した。対応済み: 正確な値に絞り込み、動作確認済み
  5. プリザンターのLoginIdはAPI経由で変更不可: /api/users/{id}/updateにLoginIdを渡すと500エラーレスポンスボディ空。管理画面からの手動変更のみ可能
  6. 氏名欄にメールアドレスが入る: Attributes.Nameがマッピングする"Name"というSAML属性をKeycloak側が送信していないAttributeマッパー未設定ため、プリザンター側が空のName属性の代わりにNameIdメールを表示に流用している。対応済み(下記「氏名連携の恒久対応」参照)
  7. Identity Provider RedirectorでDefault Identity Providerを設定してもAliasが空だと保存できず、フォールバックで意図しないパスOrganization機能等に入りInvalid username or passwordになる: Aliasは任意の名前でよいが必須項目
  8. Keycloak Admin APIのPUT /users/{id}は完全上書き: 更新したいフィールド(例: attributesだけを含めてPUTすると、リクエストに含まれない既存フィールドemail等)が消える。一度この事故で対象ユーザーのemailが消え、Keycloak側で「Update Account Information」の強制入力画面が表示される事態になった。復旧はusernameemailenabled等の既存値を含めて再度PUTすることで対応。Upsert処理では常に現在の全フィールドを読み取ってからマージして送ることが必須(④sync-keycloak.jsの実装注意点)
  9. Keycloakの「User Profile」機能が有効だと、未定義のカスタム属性はAPI経由で設定してもサイレントに無視される: Realm settings > User profile で対象の属性(fullNamepleasanterLoginIdlineworksUserId)を明示的に属性定義として追加する必要がある。この設定変更にはmanage-realm相当の強い権限が必要で、org-master-syncクライアント(manage-users/query-groupsのみでは403になる。管理コンソールでの手動作業が必要
  10. SAML MapperのUser Attribute欄の指定ミス: fullNameを指定するつもりがfirstNameになっており、氏名連携が「名だけ」表示になる不具合が発生。設定値は必ず実際の属性名と一致するか確認すること

動作確認結果

  • テストユーザー本番UserId=460、プリザンターUserId=36でSSOログイン完走を確認。LoginIdを事前にkenichiro.nogi@next-hd.co.jpKeycloakがSAML NameIdとして送信するメールアドレスに手動変更した上で、新規ユーザーが作られることなく既存ユーザーに正しく紐付いた。NameId=LoginId照合ロジックが期待通り機能することを確認済み
  • ディープリンク復帰: 未ログイン状態で任意のテーブルURLを開き、SSOログインを経由した場合、ログイン後に元のテーブルURLへ正しく戻ることを確認済み。ACS URLは「SAMLレスポンスを受け取る技術的な窓口」にすぎず、SPがRelayStateパラメータで元のURLを追跡してリダイレクトし直す仕組みのため、Valid redirect URIsを正確なACS URL単体に絞ってもこの復帰動作には影響しない
  • Valid redirect URIsを正確なACS URLhttps://nextoffice2.next-hd.net/pleasanter/Saml2/Acs)に確定済み

Keycloakを中間ユーザーDBとして使う方式プリザンターLoginId問題の解決

プリザンターのLoginIdはAPI経由で変更不可ハマった点5という制約に対し、Keycloak側にプリザンター固有のLoginIdをカスタム属性として持たせ、SAMLのNameIdをそのカスタム属性から生成する方式で解決できることを実証した。

仕組み: Keycloakの標準機能「User Attribute Mapper for NameID」を使う。ユーザーのカスタム属性例: pleasanterLoginIdに既存のプリザンターLoginIdntkco12216等、任意の文字列でよいを設定し、SAML Client側でこの属性をNameID Format: Unspecifiedとしてマッピングする。これにより、プリザンター側のLoginIdを一切変更せずにSSOを機能させられる。

検証手順と結果:

  1. プリザンターに新規テストユーザー作成UserId=592, LoginId=nameid-mapper-test、任意の非メール文字列)
  2. Keycloak Admin APIで対応するテストユーザーを作成し、attributes.pleasanterLoginId = ["nameid-mapper-test"]を設定
  3. SAML ClientにUser Attribute Mapper for NameIDAttribute: pleasanterLoginId、Name ID Format: Unspecified)を追加
  4. 検証のため一時的にBrowser Flow overrideを解除browser-idp-onlyは外部IdP自動リダイレクトのためusername/passwordログインができないためし、作成したKeycloak内部ユーザーでログイン
  5. プリザンター側でLoginId=nameid-mapper-testのユーザーとして正しく認識されることを確認

本番運用への含意: この方式により「プリザンターLoginIdをメールアドレスへ統一する」対応は不要になった。マスタテーブルの「プリザンターログインID」列の値を、④sync-keycloak.jsでKeycloakユーザーのカスタム属性pleasanterLoginIdへ書き込む処理を追加すればよい。氏名(fullName属性、氏名連携の恒久対応と同じパターンで統一的に扱える。将来のXWiki等、他システム連携でも同じ枠組みシステムごとのカスタム属性を使い回せる。

氏名連携の恒久対応(実証済み)

「氏名欄にメールアドレスが入る」ハマった点6を、pleasanterLoginIdと同じ「Keycloakカスタム属性→SAML Attribute」方式で解決した。

手順:

  1. Keycloak Admin APIで対象ユーザーにattributes.fullNameを設定(実運用では④sync-keycloak.jsが行う)
  2. Realm settings > User profile にfullName属性を追加定義ハマった点9。これをやらないと1の設定がサイレントに無視される
  3. SAML ClientにUser AttributeタイプのMapperを追加: User Attribute: fullNameSAML Attribute Name: NameSAML Attribute NameFormat: Basic
  4. 動作確認: SSOログイン後、プリザンターの氏名欄に正しく反映されることを確認済み

運用上の注意: KeycloakのfirstName/lastNameはUser Profileでrequiredのままのため、初回ログイン時に「Update Account Information」画面でこれらの入力を求められるREADME記載の既知の制約と同一事象fullName属性はfirstName/lastNameとは独立して管理され、これらが未入力でもSAML連携には影響しない。ただしfirstName/lastName自体の必須設定を緩和するかどうかは運用側で別途判断が必要(今回は未対応のまま)。

未解決・要対応事項

  • KeycloakのfirstName/lastName必須設定の扱い前項参照。初回ログイン時の入力画面をなくしたい場合はUser Profile側で必須を外す
  • sync-keycloak.jsが同期すべきKeycloakカスタム属性は8章で確定fullNamepleasanterLoginIdlineworksUserId。あわせて、Upsert時は必ず既存の全フィールドを読み取ってからマージする実装が必須ハマった点8

10. 次のステップ

  1. writing-plans skillで実装計画を作成