diff --git a/NodeSrv/apps/healthcheck-survey-bot/README.md b/NodeSrv/apps/healthcheck-survey-bot/README.md index ee7caff9..0533921d 100644 --- a/NodeSrv/apps/healthcheck-survey-bot/README.md +++ b/NodeSrv/apps/healthcheck-survey-bot/README.md @@ -42,3 +42,10 @@ Task 5(processFlow.js)の`getValidationColumnNames`はこの構造(`proces - `X-Api-Key`は`workflow_config_values`の`HEALTHCHECK_WP_API_KEY`と照合。秘密値のハードコードなし - 実際のcurl疎通テスト(本番508971書き込み・LINEWORKS実送信を伴う)は未実施。ユーザー確認後に実施する - デプロイ・活性化はサブエージェントのBashサンドボックスが実APIキー使用を一律ブロックしたためコントローラーが直接実行した(詳細: `task-10-report.md`) + +- ワークフロー `HC-WA: LINEWORKS応答受信`: **ローカルJSON作成のみ、n8n未デプロイ**(id未採番) + - ファイル: `workflows/hc-wa-lineworks-response.json`(45ノード、Webhook `healthcheck-lineworks-response` 唯一の受信口、`awaitInput`(none/date/file)3分岐のSwitch構成) + - 本タスクの担当範囲はワークフローJSONのローカル作成・検証までで、n8n Public APIの呼び出し(デプロイ・activate)は一切実施していない。コントローラー側での`node scripts/deploy-workflow.js workflows/hc-wa-lineworks-response.json`実行後、実際のワークフローIDをこの行に追記すること + - 秘密情報・SiteId等はすべて`workflow_config_values`(`bNkadTyDgDepYx2p`)から`Data Table`ノードで取得し式参照。ワークフローJSONに秘密値のハードコードなし + - 未検証の暫定実装あり(詳細は`task-11-report.md`): ①Data Table `update`操作のパラメータ形状(get/insertは実機確認済みだが、updateは本タスクで初採用のため未検証)、②Switchノード(typeVersion 3)の`rules.values[].outputKey`スキーマ、③`/api/users/get`をWhere無しで呼んだ場合のレスポンス形状(配列/`.Value`/単一オブジェクトいずれにも対応する防御的実装)、④`api/items/{SiteId}/get`(ColumnFilterHash検索)のレスポンス形状、⑤LINEWORKSファイル添付のダウンロード/Pleasanterアップロードのエンドポイント(ブリーフ記載どおり実装時にPleasanter公式マニュアルapi-attachment系で要確認のプレースホルダー) + - 設計上の注記: HC-SUB(Task 9)が`healthcheck_bot_state`に保存する`pendingProcesses`は`{processId, label, tooltip}`のみで`ValidateInputs`を持たないため、none分岐での`classifyAwaitInput`実行時はHC-WA内で`サイト設定取得`(getsite)を再実行し、ProcessId一致でフルのProcess定義を引き直す設計にした(Task 9側のファイルは変更していない) diff --git a/NodeSrv/apps/healthcheck-survey-bot/workflows/hc-wa-lineworks-response.json b/NodeSrv/apps/healthcheck-survey-bot/workflows/hc-wa-lineworks-response.json new file mode 100644 index 00000000..4d353b06 --- /dev/null +++ b/NodeSrv/apps/healthcheck-survey-bot/workflows/hc-wa-lineworks-response.json @@ -0,0 +1,1717 @@ +{ + "name": "HC-WA: LINEWORKS応答受信", + "nodes": [ + { + "id": "webhook-lineworks-response", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [ + 200, + 500 + ], + "webhookId": "healthcheck-lineworks-response", + "parameters": { + "httpMethod": "POST", + "path": "healthcheck-lineworks-response", + "responseMode": "responseNode", + "options": { + "rawBody": true + } + } + }, + { + "id": "dt-get-lineworks_bot_secret", + "name": "LINEWORKS_BOT_SECRET取得", + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1, + "position": [ + 420, + 500 + ], + "parameters": { + "operation": "get", + "dataTableId": { + "__rl": true, + "mode": "id", + "value": "bNkadTyDgDepYx2p" + }, + "filters": { + "conditions": [ + { + "keyName": "configKey", + "keyValue": "LINEWORKS_BOT_SECRET" + } + ] + }, + "returnAll": true + } + }, + { + "id": "code-verify-signature", + "name": "署名検証・送信者解決", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 640, + 500 + ], + "parameters": { + "jsCode": "// Codeノード「署名検証・送信者解決」の中身\nconst crypto = require(\"crypto\");\nfunction normalizeSignature(value) {\n return String(value || \"\").trim().replace(/^sha256=/i, \"\");\n}\nfunction safeEqual(a, b) {\n const ab = Buffer.from(String(a), \"utf8\");\n const bb = Buffer.from(String(b), \"utf8\");\n if (ab.length !== bb.length) return false;\n return crypto.timingSafeEqual(ab, bb);\n}\nfunction verifySignature(rawBody, headerSignature, botSecret) {\n const headerSig = normalizeSignature(headerSignature);\n if (!headerSig || !botSecret) return false;\n const payload = Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(String(rawBody), \"utf8\");\n const expected = crypto.createHmac(\"sha256\", botSecret).update(payload).digest(\"base64\");\n return safeEqual(headerSig, expected);\n}\n\nconst LINEWORKS_BOT_SECRET = $('LINEWORKS_BOT_SECRET取得').item.json.configValue;\n\nconst item = $input.first();\nconst headers = item.json.headers || {};\nconst rawBody = item.binary && item.binary.data\n ? Buffer.from(item.binary.data.data, \"base64\")\n : Buffer.from(JSON.stringify(item.json.body || {}), \"utf8\");\n\nif (!verifySignature(rawBody, headers[\"x-works-signature\"], LINEWORKS_BOT_SECRET)) {\n throw new Error(\"Unauthorized: signature mismatch\");\n}\n\nconst body = item.json.body || {};\nconst source = body.source || {};\nconst content = body.content || {};\n\nreturn [{\n json: {\n targetEmail: source.userId,\n contentType: content.type,\n text: content.type === \"text\" ? content.text : null,\n fileId: content.type === \"file\" ? content.fileId : null,\n },\n}];" + } + }, + { + "id": "dt-get-pleasanter_base_url_prod", + "name": "PLEASANTER_BASE_URL取得", + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1, + "position": [ + 860, + 500 + ], + "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": [ + 1080, + 500 + ], + "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": [ + 1300, + 500 + ], + "parameters": { + "operation": "get", + "dataTableId": { + "__rl": true, + "mode": "id", + "value": "bNkadTyDgDepYx2p" + }, + "filters": { + "conditions": [ + { + "keyName": "configKey", + "keyValue": "HEALTHCHECK_SITE_ID" + } + ] + }, + "returnAll": true + } + }, + { + "id": "http-target-userid-resolve", + "name": "対象者UserId解決", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1520, + 500 + ], + "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 } }) }}", + "options": {} + } + }, + { + "id": "code-identify-user", + "name": "対象者UserId特定", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1740, + 500 + ], + "parameters": { + "jsCode": "function extractMailAddresses(user) {\n if (!user) return [];\n if (user.MailAddress) return [user.MailAddress];\n if (Array.isArray(user.MailAddresses)) return user.MailAddresses;\n return [];\n}\n\nconst targetEmail = $('署名検証・送信者解決').item.json.targetEmail;\nconst responseData = $('対象者UserId解決').item.json.Response.Data;\n// 実機未検証: /api/users/get をWhere無しで呼んだ場合のレスポンス形状(配列直下 / .Value配下 / 単一オブジェクト)を防御的に吸収する\nconst candidates = Array.isArray(responseData)\n ? responseData\n : (responseData && Array.isArray(responseData.Value) ? responseData.Value : [responseData]);\n\nconst matchedUser = candidates.find((u) => extractMailAddresses(u).includes(targetEmail));\nif (!matchedUser) {\n throw new Error(\"Pleasanterユーザーが見つかりません: \" + targetEmail);\n}\n\nreturn [{\n json: {\n ...$('署名検証・送信者解決').item.json,\n pleasanterUserId: matchedUser.UserId,\n },\n}];" + } + }, + { + "id": "http-target-record-search", + "name": "対象レコード検索", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1960, + 500 + ], + "parameters": { + "method": "POST", + "url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('HEALTHCHECK_SITE_ID取得').item.json.configValue }}/get", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue, View: { ColumnFilterHash: { ClassC: String($('対象者UserId特定').item.json.pleasanterUserId) }, ColumnFilterSearchTypes: { ClassC: 'ExactMatch' } } }) }}", + "options": {} + } + }, + { + "id": "code-pick-record", + "name": "対象レコード選定", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2180, + 500 + ], + "parameters": { + "jsCode": "const responseData = $('対象レコード検索').item.json.Response.Data;\n// 実機未検証: SiteIdでの/get呼び出しレスポンスは.Value配下に配列が入る想定(公式マニュアルのサイト一覧系レスポンス形状)。単一オブジェクトの場合も防御的に吸収する\nconst records = Array.isArray(responseData)\n ? responseData\n : (responseData && Array.isArray(responseData.Value) ? responseData.Value : []);\n\nconst filtered = records.filter((r) => r.Status !== 900 && r.Status !== 910);\nconst sorted = filtered.slice().sort((a, b) => new Date(b.UpdatedTime) - new Date(a.UpdatedTime));\n\nif (sorted.length !== 1) {\n throw new Error(\"対象レコードが一意に定まりません(候補\" + sorted.length + \"件)。手動確認が必要です。\");\n}\n\nconst record = sorted[0];\n\nreturn [{\n json: {\n ...$('対象者UserId特定').item.json,\n resultId: record.ResultId,\n currentStatus: record.Status,\n },\n}];" + } + }, + { + "id": "dt-get-bot-state", + "name": "待機状態取得", + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1, + "position": [ + 2400, + 500 + ], + "parameters": { + "operation": "get", + "dataTableId": { + "__rl": true, + "mode": "id", + "value": "jqMDa2YZTI4f0iQ7" + }, + "filters": { + "conditions": [ + { + "keyName": "resultId", + "keyValue": "={{ String($('対象レコード選定').item.json.resultId) }}" + } + ] + }, + "returnAll": true + } + }, + { + "id": "http-site-settings-get", + "name": "サイト設定取得", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2620, + 500 + ], + "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": "dt-get-lw_bot_client_id", + "name": "LW_BOT_CLIENT_ID取得", + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1, + "position": [ + 2840, + 500 + ], + "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": [ + 3060, + 500 + ], + "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": [ + 3280, + 500 + ], + "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": [ + 3500, + 500 + ], + "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": [ + 3720, + 500 + ], + "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": "code-jwt-claims", + "name": "JWTクレーム組み立て", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3940, + 500 + ], + "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)の実データ構造は未調査。Task 9(HC-SUB)と同じ暫定実装を踏襲する。\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\nconst targetEmail = $('署名検証・送信者解決').item.json.targetEmail;\nconst apiUrl = 'https://www.worksapis.com/v1.0/bots/' + botId + '/users/' + targetEmail + '/messages';\n\nreturn [{\n json: {\n jwtClaims,\n apiUrl,\n botId,\n targetEmail,\n },\n}];" + } + }, + { + "id": "jwt-sign", + "name": "Sign JWT", + "type": "n8n-nodes-base.jwt", + "typeVersion": 1, + "position": [ + 4160, + 500 + ], + "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": [ + 4380, + 500 + ], + "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": "switch-await-input", + "name": "awaitInput分岐", + "type": "n8n-nodes-base.switch", + "typeVersion": 3, + "position": [ + 4600, + 500 + ], + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "conditions": [ + { + "leftValue": "={{ $('待機状態取得').item.json.awaitInput }}", + "rightValue": "none", + "operator": { + "type": "string", + "operation": "equals" + } + } + ] + }, + "outputKey": "none" + }, + { + "conditions": { + "conditions": [ + { + "leftValue": "={{ $('待機状態取得').item.json.awaitInput }}", + "rightValue": "date", + "operator": { + "type": "string", + "operation": "equals" + } + } + ] + }, + "outputKey": "date" + }, + { + "conditions": { + "conditions": [ + { + "leftValue": "={{ $('待機状態取得').item.json.awaitInput }}", + "rightValue": "file", + "operator": { + "type": "string", + "operation": "equals" + } + } + ] + }, + "outputKey": "file" + } + ] + }, + "options": {} + } + }, + { + "id": "code-match-text", + "name": "テキスト照合", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4820, + 200 + ], + "parameters": { + "jsCode": "const stateItem = $('待機状態取得').item.json;\nconst text = $('署名検証・送信者解決').item.json.text;\nlet pendingProcesses = [];\ntry {\n pendingProcesses = JSON.parse(stateItem.pendingProcesses || '[]');\n} catch (e) {\n pendingProcesses = [];\n}\n\nconst trimmed = String(text ?? '').trim();\n// Task 5 matchProcessByLabel相当(pendingProcessesの保存形状に合わせ p.label で照合)\nconst matched = pendingProcesses.find((p) => p.label === trimmed) || null;\n\nreturn [{\n json: {\n resultId: stateItem.resultId,\n matched,\n },\n}];" + } + }, + { + "id": "if-text-matched", + "name": "テキスト一致判定", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 5040, + 200 + ], + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 2 + }, + "conditions": [ + { + "id": "cond-1", + "leftValue": "={{ !!$('テキスト照合').item.json.matched }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + } + }, + { + "id": "code-classify-await-input", + "name": "入力待ち種別判定", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5260, + 100 + ], + "parameters": { + "jsCode": "function getValidationColumnNames(process) {\n if (!Array.isArray(process.ValidateInputs)) return [];\n return process.ValidateInputs.map((v) => v.ColumnName).filter(Boolean);\n}\nfunction classifyAwaitInput(process) {\n const columnNames = getValidationColumnNames(process);\n if (columnNames.length === 0) {\n return { awaitInput: \"none\", column: null };\n }\n const column = columnNames[0];\n if (column.startsWith(\"Date\")) {\n return { awaitInput: \"date\", column };\n }\n if (column.startsWith(\"Attachments\")) {\n return { awaitInput: \"file\", column };\n }\n return { awaitInput: \"none\", column: null };\n}\n\nconst matched = $('テキスト照合').item.json.matched;\nconst resultId = $('テキスト照合').item.json.resultId;\n\n// 注意: HC-SUBの「選択肢組み立て」がData Tableに保存するpendingProcessesは\n// {processId, label, tooltip}のみでValidateInputsを持たないため、ここではサイト設定を\n// 再取得してProcessId一致でValidateInputsを引き直す(Task9側の保存形状を変更しない前提の対応)\nconst siteSettings = $('サイト設定取得').item.json.Response.Data.SiteSettings;\nconst processes = siteSettings.Processes || [];\nconst fullProcess = processes.find((p) => p.Id === matched.processId);\nif (!fullProcess) {\n throw new Error('ProcessId ' + matched.processId + ' がサイト設定内に見つかりません');\n}\n\nconst classification = classifyAwaitInput(fullProcess);\n\nreturn [{\n json: {\n resultId,\n processId: matched.processId,\n awaitInput: classification.awaitInput,\n awaitColumn: classification.column,\n },\n}];" + } + }, + { + "id": "if-immediate-run", + "name": "即時実行判定", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 5480, + 100 + ], + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 2 + }, + "conditions": [ + { + "id": "cond-1", + "leftValue": "={{ $('入力待ち種別判定').item.json.awaitInput === 'none' }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + } + }, + { + "id": "exec-hc-sub-immediate", + "name": "Process実行(即時)", + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.2, + "position": [ + 5700, + 30 + ], + "parameters": { + "workflowId": { + "__rl": true, + "mode": "id", + "value": "XRqcykbG2LuAjGG2" + }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { + "resultId": "={{ $('入力待ち種別判定').item.json.resultId }}", + "processId": "={{ $('入力待ち種別判定').item.json.processId }}" + } + } + } + }, + { + "id": "dt-update-state-await", + "name": "待機状態保存(入力待ち)", + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1, + "position": [ + 5700, + 170 + ], + "parameters": { + "operation": "update", + "dataTableId": { + "__rl": true, + "mode": "id", + "value": "jqMDa2YZTI4f0iQ7" + }, + "filters": { + "conditions": [ + { + "keyName": "resultId", + "keyValue": "={{ String($('入力待ち種別判定').item.json.resultId) }}" + } + ] + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "awaitInput": "={{ $('入力待ち種別判定').item.json.awaitInput }}", + "awaitProcessId": "={{ String($('入力待ち種別判定').item.json.processId) }}", + "awaitColumn": "={{ $('入力待ち種別判定').item.json.awaitColumn }}" + }, + "schema": [ + { + "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 + } + ] + } + } + }, + { + "id": "code-prompt-message", + "name": "追加案内メッセージ組み立て", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5920, + 170 + ], + "parameters": { + "jsCode": "const awaitInput = $('入力待ち種別判定').item.json.awaitInput;\nconst text = awaitInput === 'date'\n ? '日付を入力してください(例: 2026-03-01 や 令和6年3月1日)'\n : 'ファイルを送信してください';\n\nreturn [{ json: { messageContent: { type: 'text', text } } }];" + } + }, + { + "id": "http-prompt-send", + "name": "追加案内送信", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 6140, + 170 + ], + "parameters": { + "method": "POST", + "url": "={{ $('JWTクレーム組み立て').item.json.apiUrl }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "=Bearer {{ $('アクセストークン取得').item.json.access_token }}" + }, + { + "name": "Content-Type", + "value": "application/json;charset=UTF-8" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ content: $('追加案内メッセージ組み立て').item.json.messageContent }) }}", + "options": {} + } + }, + { + "id": "exec-hc-sub-fallback", + "name": "Process実行(フォールバック)", + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.2, + "position": [ + 5480, + 350 + ], + "parameters": { + "workflowId": { + "__rl": true, + "mode": "id", + "value": "XRqcykbG2LuAjGG2" + }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { + "resultId": "={{ $('テキスト照合').item.json.resultId }}", + "processId": "" + } + } + } + }, + { + "id": "code-date-parse", + "name": "日付パース", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4820, + 500 + ], + "parameters": { + "jsCode": "const ERA_INFO = {\n \"令和\": 2018,\n \"平成\": 1988,\n \"昭和\": 1925,\n \"大正\": 1911,\n};\n\nconst ERA_ALIASES = { R: \"令和\", H: \"平成\", S: \"昭和\", T: \"大正\" };\n\nfunction pad2(value) {\n return String(value).padStart(2, \"0\");\n}\n\nfunction finalizeDateParts(year, month, day) {\n if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) {\n throw new Error(\"日付形式で回答してください(例: 2026-03-01)\");\n }\n const date = new Date(year, month - 1, day);\n if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {\n throw new Error(\"存在しない日付です\");\n }\n return `${year}-${pad2(month)}-${pad2(day)}`;\n}\n\nfunction tryParseEraDate(compact) {\n const match = compact.match(/^(令和|平成|昭和|大正|R|H|S|T)(.+)$/i);\n if (!match) return null;\n\n let era = match[1];\n if (/^[RHST]$/i.test(era)) {\n era = ERA_ALIASES[era.toUpperCase()] || era;\n }\n if (!ERA_INFO[era]) return null;\n\n const normalized = match[2]\n .replace(/年/g, \"-\")\n .replace(/月/g, \"-\")\n .replace(/日/g, \"\")\n .replace(/[.\\/]/g, \"-\");\n const parts = normalized.split(\"-\").filter((part) => part.length > 0);\n if (parts.length < 3) {\n throw new Error(\"月と日まで入力してください(例: 令和6年3月1日)\");\n }\n\n const eraYear = Number(parts[0]);\n const month = Number(parts[1]);\n const day = Number(parts[2]);\n if (!Number.isFinite(eraYear) || !Number.isFinite(month) || !Number.isFinite(day)) {\n throw new Error(\"日付形式で回答してください(例: 令和6年3月1日)\");\n }\n\n return finalizeDateParts(ERA_INFO[era] + eraYear, month, day);\n}\n\nfunction tryParseMonthDay(compact) {\n const match = compact.match(/^(\\d{1,2})(?:月|\\/|-|\\.)(\\d{1,2})(?:日)?$/);\n if (!match) return null;\n const currentYear = new Date().getFullYear();\n return finalizeDateParts(currentYear, Number(match[1]), Number(match[2]));\n}\n\nfunction parseDateInput(value) {\n const trimmed = String(value ?? \"\").trim();\n if (!trimmed) {\n throw new Error(\"日付が空です\");\n }\n const compact = trimmed.replace(/\\s+/g, \"\");\n\n const eraResult = tryParseEraDate(compact);\n if (eraResult) return eraResult;\n\n const monthDayResult = tryParseMonthDay(compact);\n if (monthDayResult) return monthDayResult;\n\n const normalized = compact\n .replace(/年/g, \"-\")\n .replace(/月/g, \"-\")\n .replace(/日/g, \"\")\n .replace(/[.\\/]/g, \"-\");\n const isoParts = normalized.split(\"-\").filter((part) => part.length > 0);\n if (isoParts.length === 3 && isoParts[0].length >= 4) {\n return finalizeDateParts(Number(isoParts[0]), Number(isoParts[1]), Number(isoParts[2]));\n }\n\n const parsed = new Date(trimmed);\n if (Number.isNaN(parsed.getTime())) {\n throw new Error(\"日付形式で回答してください(例: 2026-03-01 や 令和6年3月1日)\");\n }\n return finalizeDateParts(parsed.getFullYear(), parsed.getMonth() + 1, parsed.getDate());\n}\n\nconst text = $('署名検証・送信者解決').item.json.text;\nconst stateItem = $('待機状態取得').item.json;\nlet result;\ntry {\n result = { success: true, parsedDate: parseDateInput(text) };\n} catch (err) {\n result = { success: false, errorMessage: err.message };\n}\n\nreturn [{\n json: {\n resultId: stateItem.resultId,\n awaitProcessId: stateItem.awaitProcessId,\n awaitColumn: stateItem.awaitColumn,\n ...result,\n },\n}];" + } + }, + { + "id": "if-date-parse-ok", + "name": "日付パース成否判定", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 5040, + 500 + ], + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 2 + }, + "conditions": [ + { + "id": "cond-1", + "leftValue": "={{ $('日付パース').item.json.success }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + } + }, + { + "id": "http-date-column-update", + "name": "該当列update", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5260, + 420 + ], + "parameters": { + "method": "POST", + "url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('日付パース').item.json.resultId }}/update", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue, DateHash: { [$('日付パース').item.json.awaitColumn]: $('日付パース').item.json.parsedDate } }) }}", + "options": {} + } + }, + { + "id": "dt-update-state-reset-date", + "name": "待機状態リセット(日付)", + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1, + "position": [ + 5480, + 420 + ], + "parameters": { + "operation": "update", + "dataTableId": { + "__rl": true, + "mode": "id", + "value": "jqMDa2YZTI4f0iQ7" + }, + "filters": { + "conditions": [ + { + "keyName": "resultId", + "keyValue": "={{ String($('日付パース').item.json.resultId) }}" + } + ] + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "awaitInput": "none", + "awaitProcessId": "", + "awaitColumn": "" + }, + "schema": [ + { + "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 + } + ] + } + } + }, + { + "id": "exec-hc-sub-date-done", + "name": "Process実行(日付完了)", + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.2, + "position": [ + 5700, + 420 + ], + "parameters": { + "workflowId": { + "__rl": true, + "mode": "id", + "value": "XRqcykbG2LuAjGG2" + }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { + "resultId": "={{ $('日付パース').item.json.resultId }}", + "processId": "={{ $('日付パース').item.json.awaitProcessId }}" + } + } + } + }, + { + "id": "code-date-error-message", + "name": "日付エラーメッセージ組み立て", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5260, + 600 + ], + "parameters": { + "jsCode": "const errorMessage = $('日付パース').item.json.errorMessage;\nreturn [{ json: { messageContent: { type: 'text', text: errorMessage } } }];" + } + }, + { + "id": "http-date-error-send", + "name": "日付エラー送信", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5480, + 600 + ], + "parameters": { + "method": "POST", + "url": "={{ $('JWTクレーム組み立て').item.json.apiUrl }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "=Bearer {{ $('アクセストークン取得').item.json.access_token }}" + }, + { + "name": "Content-Type", + "value": "application/json;charset=UTF-8" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ content: $('日付エラーメッセージ組み立て').item.json.messageContent }) }}", + "options": {} + } + }, + { + "id": "if-content-is-file", + "name": "ファイル判定", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 4820, + 800 + ], + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 2 + }, + "conditions": [ + { + "id": "cond-1", + "leftValue": "={{ $('署名検証・送信者解決').item.json.contentType === 'file' }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + } + }, + { + "id": "http-lineworks-file-download", + "name": "LINEWORKSファイルダウンロード", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 720 + ], + "parameters": { + "method": "GET", + "url": "={{ 'https://www.worksapis.com/v1.0/bots/' + $('JWTクレーム組み立て').item.json.botId + '/attachments/' + $('署名検証・送信者解決').item.json.fileId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "=Bearer {{ $('アクセストークン取得').item.json.access_token }}" + } + ] + }, + "options": { + "response": { + "response": { + "responseFormat": "file" + } + } + } + } + }, + { + "id": "http-pleasanter-attachment-upload", + "name": "Pleasanter添付アップロード", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5260, + 720 + ], + "parameters": { + "method": "POST", + "url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/binaries/upload", + "sendBody": true, + "contentType": "multipart-form-data", + "bodyParameters": { + "parameters": [ + { + "parameterType": "formData", + "name": "ApiKey", + "value": "={{ $('PLEASANTER_API_KEY取得').item.json.configValue }}" + }, + { + "parameterType": "formBinaryData", + "name": "file", + "inputDataFieldName": "data" + } + ] + }, + "options": {} + } + }, + { + "id": "dt-update-state-reset-file", + "name": "待機状態リセット(ファイル)", + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1, + "position": [ + 5480, + 720 + ], + "parameters": { + "operation": "update", + "dataTableId": { + "__rl": true, + "mode": "id", + "value": "jqMDa2YZTI4f0iQ7" + }, + "filters": { + "conditions": [ + { + "keyName": "resultId", + "keyValue": "={{ String($('待機状態取得').item.json.resultId) }}" + } + ] + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "awaitInput": "none", + "awaitProcessId": "", + "awaitColumn": "" + }, + "schema": [ + { + "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 + } + ] + } + } + }, + { + "id": "exec-hc-sub-file-done", + "name": "Process実行(ファイル完了)", + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.2, + "position": [ + 5700, + 720 + ], + "parameters": { + "workflowId": { + "__rl": true, + "mode": "id", + "value": "XRqcykbG2LuAjGG2" + }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { + "resultId": "={{ $('待機状態取得').item.json.resultId }}", + "processId": "={{ $('待機状態取得').item.json.awaitProcessId }}" + } + } + } + }, + { + "id": "code-file-missing-message", + "name": "ファイル未着メッセージ組み立て", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5040, + 900 + ], + "parameters": { + "jsCode": "return [{ json: { messageContent: { type: 'text', text: 'ファイルを送信してください(テキストではなくファイルを添付して送信してください)' } } }];" + } + }, + { + "id": "http-file-missing-send", + "name": "ファイル未着送信", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5260, + 900 + ], + "parameters": { + "method": "POST", + "url": "={{ $('JWTクレーム組み立て').item.json.apiUrl }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "=Bearer {{ $('アクセストークン取得').item.json.access_token }}" + }, + { + "name": "Content-Type", + "value": "application/json;charset=UTF-8" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ content: $('ファイル未着メッセージ組み立て').item.json.messageContent }) }}", + "options": {} + } + }, + { + "id": "respond-ok", + "name": "Respond to Webhook", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.1, + "position": [ + 6360, + 500 + ], + "parameters": { + "respondWith": "json", + "responseBody": "={{ JSON.stringify({ result: \"ok\" }) }}", + "options": {} + } + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "LINEWORKS_BOT_SECRET取得", + "type": "main", + "index": 0 + } + ] + ] + }, + "LINEWORKS_BOT_SECRET取得": { + "main": [ + [ + { + "node": "署名検証・送信者解決", + "type": "main", + "index": 0 + } + ] + ] + }, + "署名検証・送信者解決": { + "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": "対象者UserId解決", + "type": "main", + "index": 0 + } + ] + ] + }, + "対象者UserId解決": { + "main": [ + [ + { + "node": "対象者UserId特定", + "type": "main", + "index": 0 + } + ] + ] + }, + "対象者UserId特定": { + "main": [ + [ + { + "node": "対象レコード検索", + "type": "main", + "index": 0 + } + ] + ] + }, + "対象レコード検索": { + "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": "JWTクレーム組み立て", + "type": "main", + "index": 0 + } + ] + ] + }, + "JWTクレーム組み立て": { + "main": [ + [ + { + "node": "Sign JWT", + "type": "main", + "index": 0 + } + ] + ] + }, + "Sign JWT": { + "main": [ + [ + { + "node": "アクセストークン取得", + "type": "main", + "index": 0 + } + ] + ] + }, + "アクセストークン取得": { + "main": [ + [ + { + "node": "awaitInput分岐", + "type": "main", + "index": 0 + } + ] + ] + }, + "awaitInput分岐": { + "main": [ + [ + { + "node": "テキスト照合", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "日付パース", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "ファイル判定", + "type": "main", + "index": 0 + } + ] + ] + }, + "テキスト照合": { + "main": [ + [ + { + "node": "テキスト一致判定", + "type": "main", + "index": 0 + } + ] + ] + }, + "テキスト一致判定": { + "main": [ + [ + { + "node": "入力待ち種別判定", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Process実行(フォールバック)", + "type": "main", + "index": 0 + } + ] + ] + }, + "入力待ち種別判定": { + "main": [ + [ + { + "node": "即時実行判定", + "type": "main", + "index": 0 + } + ] + ] + }, + "即時実行判定": { + "main": [ + [ + { + "node": "Process実行(即時)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "待機状態保存(入力待ち)", + "type": "main", + "index": 0 + } + ] + ] + }, + "待機状態保存(入力待ち)": { + "main": [ + [ + { + "node": "追加案内メッセージ組み立て", + "type": "main", + "index": 0 + } + ] + ] + }, + "追加案内メッセージ組み立て": { + "main": [ + [ + { + "node": "追加案内送信", + "type": "main", + "index": 0 + } + ] + ] + }, + "Process実行(即時)": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "追加案内送信": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "Process実行(フォールバック)": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "日付パース": { + "main": [ + [ + { + "node": "日付パース成否判定", + "type": "main", + "index": 0 + } + ] + ] + }, + "日付パース成否判定": { + "main": [ + [ + { + "node": "該当列update", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "日付エラーメッセージ組み立て", + "type": "main", + "index": 0 + } + ] + ] + }, + "該当列update": { + "main": [ + [ + { + "node": "待機状態リセット(日付)", + "type": "main", + "index": 0 + } + ] + ] + }, + "待機状態リセット(日付)": { + "main": [ + [ + { + "node": "Process実行(日付完了)", + "type": "main", + "index": 0 + } + ] + ] + }, + "日付エラーメッセージ組み立て": { + "main": [ + [ + { + "node": "日付エラー送信", + "type": "main", + "index": 0 + } + ] + ] + }, + "Process実行(日付完了)": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "日付エラー送信": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "ファイル判定": { + "main": [ + [ + { + "node": "LINEWORKSファイルダウンロード", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "ファイル未着メッセージ組み立て", + "type": "main", + "index": 0 + } + ] + ] + }, + "LINEWORKSファイルダウンロード": { + "main": [ + [ + { + "node": "Pleasanter添付アップロード", + "type": "main", + "index": 0 + } + ] + ] + }, + "Pleasanter添付アップロード": { + "main": [ + [ + { + "node": "待機状態リセット(ファイル)", + "type": "main", + "index": 0 + } + ] + ] + }, + "待機状態リセット(ファイル)": { + "main": [ + [ + { + "node": "Process実行(ファイル完了)", + "type": "main", + "index": 0 + } + ] + ] + }, + "ファイル未着メッセージ組み立て": { + "main": [ + [ + { + "node": "ファイル未着送信", + "type": "main", + "index": 0 + } + ] + ] + }, + "Process実行(ファイル完了)": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + }, + "ファイル未着送信": { + "main": [ + [ + { + "node": "Respond to Webhook", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1" + } +}