{ "name": "HC-SUB: プロセス実行と案内送信", "nodes": [ { "id": "trigger-1", "name": "Execute Workflow Trigger", "type": "n8n-nodes-base.executeWorkflowTrigger", "typeVersion": 1.1, "position": [ 220, 300 ], "parameters": { "workflowInputs": { "values": [ { "name": "resultId" }, { "name": "processId" } ] } } }, { "id": "dt-get-pleasanter_base_url_prod", "name": "PLEASANTER_BASE_URL取得", "type": "n8n-nodes-base.dataTable", "typeVersion": 1, "position": [ 440, 300 ], "parameters": { "operation": "get", "dataTableId": { "__rl": true, "mode": "id", "value": "bNkadTyDgDepYx2p" }, "filters": { "conditions": [ { "keyName": "configKey", "keyValue": "PLEASANTER_BASE_URL_PROD" } ] }, "returnAll": true } }, { "id": "dt-get-pleasanter_api_key_prod", "name": "PLEASANTER_API_KEY取得", "type": "n8n-nodes-base.dataTable", "typeVersion": 1, "position": [ 660, 300 ], "parameters": { "operation": "get", "dataTableId": { "__rl": true, "mode": "id", "value": "bNkadTyDgDepYx2p" }, "filters": { "conditions": [ { "keyName": "configKey", "keyValue": "PLEASANTER_API_KEY_PROD" } ] }, "returnAll": true } }, { "id": "dt-get-healthcheck_site_id", "name": "HEALTHCHECK_SITE_ID取得", "type": "n8n-nodes-base.dataTable", "typeVersion": 1, "position": [ 880, 300 ], "parameters": { "operation": "get", "dataTableId": { "__rl": true, "mode": "id", "value": "bNkadTyDgDepYx2p" }, "filters": { "conditions": [ { "keyName": "configKey", "keyValue": "HEALTHCHECK_SITE_ID" } ] }, "returnAll": true } }, { "id": "if-processid-exists", "name": "IF processId exists", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 1100, 300 ], "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose", "version": 2 }, "conditions": [ { "id": "cond-processid-truthy", "leftValue": "={{ !!$('Execute Workflow Trigger').item.json.processId }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} } }, { "id": "http-process-run", "name": "Process実行", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1320, 300 ], "parameters": { "method": "POST", "url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('Execute Workflow Trigger').item.json.resultId }}/update", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue, ProcessId: Number($('Execute Workflow Trigger').item.json.processId) }) }}", "options": {} } }, { "id": "http-record-get", "name": "レコード取得", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1540, 300 ], "parameters": { "method": "POST", "url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('Execute Workflow Trigger').item.json.resultId }}/get", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue }) }}", "options": {} } }, { "id": "http-site-settings-get", "name": "サイト設定取得", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1760, 300 ], "parameters": { "method": "POST", "url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('HEALTHCHECK_SITE_ID取得').item.json.configValue }}/getsite", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue }) }}", "options": {} } }, { "id": "code-build-options", "name": "選択肢組み立て", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1980, 300 ], "parameters": { "jsCode": "function isUnsetSentinel(value) {\n return typeof value === \"string\" && value.startsWith(\"1899\");\n}\nfunction fillTemplate(template, columns, valueHash) {\n const labelToColumnName = new Map();\n for (const column of columns) {\n if (column.LabelText) labelToColumnName.set(column.LabelText, column.ColumnName);\n }\n return template.replace(/\\{([^{}]+)\\}/g, (matched, label) => {\n const columnName = labelToColumnName.get(label);\n if (!columnName) return matched;\n const value = valueHash[columnName];\n if (value === undefined || value === null || value === \"\" || isUnsetSentinel(value)) return \"未設定\";\n return String(value);\n });\n}\nfunction extractProcessesForStatus(processes, status) {\n return processes.filter((p) => p.CurrentStatus === status || p.CurrentStatus === -1);\n}\n\nconst record = $('レコード取得').item.json.Response.Data;\nconst siteSettings = $('サイト設定取得').item.json.Response.Data.SiteSettings;\nconst columns = siteSettings.Columns || [];\nconst processes = siteSettings.Processes || [];\n\nconst valueHash = {\n ...record.ClassHash, ...record.NumHash, ...record.DateHash, ...record.DescriptionHash,\n};\n\nconst candidates = extractProcessesForStatus(processes, record.Status);\nconst options = candidates.map((p) => ({\n processId: p.Id,\n label: p.DisplayName || p.Name,\n tooltip: fillTemplate(p.ToolTip || \"\", columns, valueHash),\n}));\n\nreturn [{\n json: {\n resultId: record.ResultId,\n currentStatus: record.Status,\n options,\n classCUserId: record.ClassHash.ClassC,\n },\n}];" } }, { "id": "dt-get-lw_bot_client_id", "name": "LW_BOT_CLIENT_ID取得", "type": "n8n-nodes-base.dataTable", "typeVersion": 1, "position": [ 2200, 300 ], "parameters": { "operation": "get", "dataTableId": { "__rl": true, "mode": "id", "value": "bNkadTyDgDepYx2p" }, "filters": { "conditions": [ { "keyName": "configKey", "keyValue": "LW_BOT_CLIENT_ID" } ] }, "returnAll": true } }, { "id": "dt-get-lw_bot_client_secret", "name": "LW_BOT_CLIENT_SECRET取得", "type": "n8n-nodes-base.dataTable", "typeVersion": 1, "position": [ 2420, 300 ], "parameters": { "operation": "get", "dataTableId": { "__rl": true, "mode": "id", "value": "bNkadTyDgDepYx2p" }, "filters": { "conditions": [ { "keyName": "configKey", "keyValue": "LW_BOT_CLIENT_SECRET" } ] }, "returnAll": true } }, { "id": "dt-get-lw_bot_service_account", "name": "LW_BOT_SERVICE_ACCOUNT取得", "type": "n8n-nodes-base.dataTable", "typeVersion": 1, "position": [ 2640, 300 ], "parameters": { "operation": "get", "dataTableId": { "__rl": true, "mode": "id", "value": "bNkadTyDgDepYx2p" }, "filters": { "conditions": [ { "keyName": "configKey", "keyValue": "LW_BOT_SERVICE_ACCOUNT" } ] }, "returnAll": true } }, { "id": "dt-get-lineworks_bot_master_site_id", "name": "LINEWORKS_BOT_MASTER_SITE_ID取得", "type": "n8n-nodes-base.dataTable", "typeVersion": 1, "position": [ 2860, 300 ], "parameters": { "operation": "get", "dataTableId": { "__rl": true, "mode": "id", "value": "bNkadTyDgDepYx2p" }, "filters": { "conditions": [ { "keyName": "configKey", "keyValue": "LINEWORKS_BOT_MASTER_SITE_ID" } ] }, "returnAll": true } }, { "id": "http-bot-master-get", "name": "Botマスタ取得", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 3080, 300 ], "parameters": { "method": "POST", "url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('LINEWORKS_BOT_MASTER_SITE_ID取得').item.json.configValue }}/getsite", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue }) }}", "options": {} } }, { "id": "http-target-user-get", "name": "対象者メール解決", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 3300, 300 ], "parameters": { "method": "POST", "url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/users/get", "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue, View: { ApiGetMailAddresses: true }, Where: { UserId: $('選択肢組み立て').item.json.classCUserId } }) }}", "options": {} } }, { "id": "code-jwt-claims", "name": "JWTクレーム組み立て", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3520, 300 ], "parameters": { "jsCode": "const now = Math.floor(Date.now() / 1000);\nconst jwtClaims = JSON.stringify({\n iss: $('LW_BOT_CLIENT_ID取得').item.json.configValue,\n sub: $('LW_BOT_SERVICE_ACCOUNT取得').item.json.configValue,\n iat: now,\n exp: now + 3600,\n aud: 'https://auth.worksmobile.com/oauth2/v2.0/token',\n});\n\n// --- Bot ID解決 ---\n// TODO(Task 12): 484184(LINEWORKS_BOT_MASTER_SITE_ID)の実データ構造は未調査\n// (このワークフローは新規追加のBrief上、実データ確認前に構築している)。\n// Task 12で508971のProcess⇔Bot対応表を設計する際、実データに合わせて\n// この関数を書き換えるか、Execute Workflow Triggerの入力にbotIdを追加して\n// 呼び出し元(WP/WA)から明示的に渡す方式へ変更すること。\n// 暫定実装: getsiteのSiteSettings直下にBotIds配列がある、または\n// Processes[].BotIdが定義されているケースを想定し、最初の1件を採用する。\n// どちらの構造も無ければ、誤ったBotへの送信を避けるため明示的にエラーとする。\nconst botMasterSiteSettings = $('Botマスタ取得').item.json.Response.Data.SiteSettings;\nconst candidateBotIds = botMasterSiteSettings.BotIds\n || (botMasterSiteSettings.Processes || []).map((p) => p.BotId).filter(Boolean);\nconst botId = candidateBotIds && candidateBotIds[0];\nif (!botId) {\n throw new Error('BotIdを484184(LINEWORKS_BOT_MASTER_SITE_ID)から解決できませんでした。Task 12でBot選択ロジックを実装してください。');\n}\n\n// --- 送信先メールアドレス解決 ---\n// Pleasanter /api/users/get のレスポンス形状は実機未検証のため、配列/単一\n// オブジェクトどちらでも動くように防御的に処理する。\nconst usersResponseData = $('対象者メール解決').item.json.Response.Data;\nconst userRecord = Array.isArray(usersResponseData) ? usersResponseData[0] : usersResponseData;\nif (!userRecord) {\n throw new Error('対象者メール解決レスポンスからユーザー情報を取得できませんでした。');\n}\nconst targetEmail = userRecord.MailAddress\n || (Array.isArray(userRecord.MailAddresses) && userRecord.MailAddresses[0])\n || null;\nif (!targetEmail) {\n throw new Error('対象者のメールアドレスを解決できませんでした。');\n}\n\nconst apiUrl = `https://www.worksapis.com/v1.0/bots/${botId}/users/${targetEmail}/messages`;\n\n// --- button_template組み立て ---\nconst options = $('選択肢組み立て').item.json.options;\nconst resultId = $('選択肢組み立て').item.json.resultId;\nconst actions = options.map((opt) => ({\n type: 'message',\n label: opt.label,\n postback: JSON.stringify({ resultId, processId: opt.processId }),\n displayText: opt.label,\n}));\n\nconst messageContent = {\n type: 'button_template',\n contentText: options.map((opt) => `${opt.label}: ${opt.tooltip}`).join('\\n') || '対応可能な操作がありません',\n actions,\n};\n\nreturn [{\n json: {\n jwtClaims,\n apiUrl,\n messageContent,\n targetEmail,\n resultId,\n },\n}];" } }, { "id": "jwt-sign", "name": "Sign JWT", "type": "n8n-nodes-base.jwt", "typeVersion": 1, "position": [ 3740, 300 ], "parameters": { "operation": "sign", "useJson": true, "claimsJson": "={{ $json.jwtClaims }}", "options": { "algorithm": "RS256" } }, "credentials": { "jwtAuth": { "id": "Hw0qlEaGfLPnQWp1", "name": "LINEWORKS Bot Private Key (v4)" } } }, { "id": "http-access-token", "name": "アクセストークン取得", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 3960, 300 ], "parameters": { "method": "POST", "url": "https://auth.worksmobile.com/oauth2/v2.0/token", "sendBody": true, "contentType": "form-urlencoded", "bodyParameters": { "parameters": [ { "name": "assertion", "value": "={{ $json.token }}" }, { "name": "grant_type", "value": "urn:ietf:params:oauth:grant-type:jwt-bearer" }, { "name": "client_id", "value": "={{ $('LW_BOT_CLIENT_ID取得').item.json.configValue }}" }, { "name": "client_secret", "value": "={{ $('LW_BOT_CLIENT_SECRET取得').item.json.configValue }}" }, { "name": "scope", "value": "bot" } ] }, "options": {} } }, { "id": "http-lineworks-send", "name": "LINEWORKS送信", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 4180, 300 ], "parameters": { "method": "POST", "url": "={{ $('JWTクレーム組み立て').item.json.apiUrl }}", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "Authorization", "value": "=Bearer {{ $json.access_token }}" }, { "name": "Content-Type", "value": "application/json;charset=UTF-8" } ] }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ content: $('JWTクレーム組み立て').item.json.messageContent }) }}", "options": {} } }, { "id": "dt-state-insert", "name": "状態更新", "type": "n8n-nodes-base.dataTable", "typeVersion": 1, "position": [ 4400, 300 ], "parameters": { "operation": "insert", "dataTableId": { "__rl": true, "mode": "id", "value": "jqMDa2YZTI4f0iQ7" }, "columns": { "mappingMode": "defineBelow", "value": { "resultId": "={{ $('選択肢組み立て').item.json.resultId }}", "targetEmail": "={{ $('JWTクレーム組み立て').item.json.targetEmail }}", "currentStatus": "={{ String($('選択肢組み立て').item.json.currentStatus) }}", "pendingProcesses": "={{ JSON.stringify($('選択肢組み立て').item.json.options) }}", "awaitInput": "none", "awaitProcessId": "", "awaitColumn": "" }, "schema": [ { "id": "resultId", "displayName": "resultId", "required": false, "type": "string", "canBeUsedToMatch": true }, { "id": "targetEmail", "displayName": "targetEmail", "required": false, "type": "string", "canBeUsedToMatch": true }, { "id": "currentStatus", "displayName": "currentStatus", "required": false, "type": "string", "canBeUsedToMatch": true }, { "id": "pendingProcesses", "displayName": "pendingProcesses", "required": false, "type": "string", "canBeUsedToMatch": true }, { "id": "awaitInput", "displayName": "awaitInput", "required": false, "type": "string", "canBeUsedToMatch": true }, { "id": "awaitProcessId", "displayName": "awaitProcessId", "required": false, "type": "string", "canBeUsedToMatch": true }, { "id": "awaitColumn", "displayName": "awaitColumn", "required": false, "type": "string", "canBeUsedToMatch": true } ] } } } ], "connections": { "Execute Workflow Trigger": { "main": [ [ { "node": "PLEASANTER_BASE_URL取得", "type": "main", "index": 0 } ] ] }, "PLEASANTER_BASE_URL取得": { "main": [ [ { "node": "PLEASANTER_API_KEY取得", "type": "main", "index": 0 } ] ] }, "PLEASANTER_API_KEY取得": { "main": [ [ { "node": "HEALTHCHECK_SITE_ID取得", "type": "main", "index": 0 } ] ] }, "HEALTHCHECK_SITE_ID取得": { "main": [ [ { "node": "IF processId exists", "type": "main", "index": 0 } ] ] }, "IF processId exists": { "main": [ [ { "node": "Process実行", "type": "main", "index": 0 } ], [ { "node": "レコード取得", "type": "main", "index": 0 } ] ] }, "Process実行": { "main": [ [ { "node": "レコード取得", "type": "main", "index": 0 } ] ] }, "レコード取得": { "main": [ [ { "node": "サイト設定取得", "type": "main", "index": 0 } ] ] }, "サイト設定取得": { "main": [ [ { "node": "選択肢組み立て", "type": "main", "index": 0 } ] ] }, "選択肢組み立て": { "main": [ [ { "node": "LW_BOT_CLIENT_ID取得", "type": "main", "index": 0 } ] ] }, "LW_BOT_CLIENT_ID取得": { "main": [ [ { "node": "LW_BOT_CLIENT_SECRET取得", "type": "main", "index": 0 } ] ] }, "LW_BOT_CLIENT_SECRET取得": { "main": [ [ { "node": "LW_BOT_SERVICE_ACCOUNT取得", "type": "main", "index": 0 } ] ] }, "LW_BOT_SERVICE_ACCOUNT取得": { "main": [ [ { "node": "LINEWORKS_BOT_MASTER_SITE_ID取得", "type": "main", "index": 0 } ] ] }, "LINEWORKS_BOT_MASTER_SITE_ID取得": { "main": [ [ { "node": "Botマスタ取得", "type": "main", "index": 0 } ] ] }, "Botマスタ取得": { "main": [ [ { "node": "対象者メール解決", "type": "main", "index": 0 } ] ] }, "対象者メール解決": { "main": [ [ { "node": "JWTクレーム組み立て", "type": "main", "index": 0 } ] ] }, "JWTクレーム組み立て": { "main": [ [ { "node": "Sign JWT", "type": "main", "index": 0 } ] ] }, "Sign JWT": { "main": [ [ { "node": "アクセストークン取得", "type": "main", "index": 0 } ] ] }, "アクセストークン取得": { "main": [ [ { "node": "LINEWORKS送信", "type": "main", "index": 0 } ] ] }, "LINEWORKS送信": { "main": [ [ { "node": "状態更新", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1" } }