{
  "name": "発注メールから freee の請求書を作る（明細と値段は見積書から）",
  "nodes": [
    {
      "id": "c8000000-0000-4000-8000-000000000001",
      "name": "発注メール",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ],
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "simple": false,
        "filters": {
          "q": "subject:(発注 OR 注文) -subject:\"[n8n 請求]\""
        },
        "options": {}
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000002",
      "name": "メールを読む",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ],
      "parameters": {
        "jsCode": "// Sender, subject and the plain text of the mail.\nconst m = $json;\nconst from = (m.from && m.from.value && m.from.value[0]) || {};\nconst text = String(m.text || m.textAsHtml || m.snippet || '').replace(/\\r/g, '').trim();\nreturn [{ json: { source: 'gmail', from_email: String(from.address || '').toLowerCase(), subject: m.subject || '', text } }];"
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000003",
      "name": "発注を貼るフォーム",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.6,
      "position": [
        0,
        300
      ],
      "parameters": {
        "formTitle": "発注から freee の請求書を作る",
        "formDescription": "お客様の発注（見積書への返信など）を貼ってください。そのお客様の見積書から、freee に請求書が「送付待ち」で作られます。freee から送るまで、お客様には何も届きません。",
        "formFields": {
          "values": [
            {
              "fieldLabel": "お客様のメールアドレス",
              "fieldName": "Email",
              "fieldType": "email",
              "requiredField": false,
              "placeholder": "buyer@example.com"
            },
            {
              "fieldLabel": "発注の内容",
              "fieldName": "Order",
              "fieldType": "textarea",
              "requiredField": true,
              "placeholder": "Q-0000000004 の内容で発注します。ただし、パンフレットは800部に変更してください。\n\n株式会社サンプル商事 佐藤"
            }
          ]
        },
        "responseMode": "lastNode",
        "options": {
          "path": "order-to-freee-invoice"
        }
      },
      "webhookId": "5e1b7c3a-9d24-4f68-a0e3-2c8d6f4b1a97"
    },
    {
      "id": "c8000000-0000-4000-8000-000000000004",
      "name": "フォームを読む",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        300
      ],
      "parameters": {
        "jsCode": "return [{ json: { source: 'form', from_email: String($json.Email || '').trim().toLowerCase(), subject: 'フォームからの発注', text: String($json.Order || '').replace(/\\r/g, '').trim() } }];"
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000005",
      "name": "設定",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        440,
        150
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": true,
        "assignments": {
          "assignments": [
            {
              "id": "c01",
              "name": "freee_company_id",
              "value": "PASTE_YOUR_FREEE_COMPANY_ID",
              "type": "string"
            },
            {
              "id": "c02",
              "name": "staff_email",
              "value": "staff@example.com",
              "type": "string"
            },
            {
              "id": "c03",
              "name": "payment_terms",
              "value": "end_of_next_month",
              "type": "string"
            },
            {
              "id": "c04",
              "name": "payment_days",
              "value": 30,
              "type": "number"
            },
            {
              "id": "c05",
              "name": "max_quotations",
              "value": 5,
              "type": "number"
            },
            {
              "id": "c06",
              "name": "timezone",
              "value": "Asia/Tokyo",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000006",
      "name": "freee の取引先を読む",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        150
      ],
      "parameters": {
        "url": "=https://api.freee.co.jp/api/1/partners?company_id={{ $json.freee_company_id }}&limit=3000",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000007",
      "name": "お客様を探す",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        150
      ],
      "parameters": {
        "jsCode": "// The customer must be an existing freee partner: the sender's e-mail, or exactly one partner name written in the mail.\nconst c = $('設定').first().json;\nif ($json.statusCode !== 200) return [{ json: { ok: false, problem: 'freee の取引先を読めませんでした（HTTP ' + $json.statusCode + '）。freee の認証情報、アプリの権限、freee_company_id を確かめてください。' } }];\nif (!c.text) return [{ json: { ok: false, problem: '発注の内容が空です。' } }];\nconst norm = (s) => String(s ?? '').normalize('NFKC').toLowerCase().replace(/\\s/g, '');\nconst partners = ($json.body.partners || []).filter((p) => p.available !== false);\nlet partner = c.from_email ? partners.find((p) => String(p.email || '').toLowerCase() === c.from_email) : null;\nlet by = partner ? 'メールアドレス ' + c.from_email : '';\nif (!partner) {\n  const text = norm(c.text);\n  const hits = partners.filter((p) => [p.name, p.long_name].some((v) => v && norm(v).length >= 4 && text.includes(norm(v))));\n  if (hits.length === 1) { partner = hits[0]; by = '会社名「' + partner.name + '」（メールの中）'; }\n}\nif (!partner) return [{ json: { ok: false, problem: 'お客様が freee の取引先に見つかりませんでした' + (c.from_email ? '（メールアドレス：' + c.from_email + ')' : '') + '。請求書は作っていません。' } }];\nreturn [{ json: { ok: true, partner: { id: partner.id, name: partner.name, by } } }];"
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000008",
      "name": "お客様が見つかった？",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1100,
        150
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "ok",
              "leftValue": "={{ $json.ok }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000009",
      "name": "お客様の見積書を読む",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        50
      ],
      "parameters": {
        "url": "=https://api.freee.co.jp/iv/quotations?company_id={{ $('設定').first().json.freee_company_id }}&partner_ids={{ $json.partner.id }}&cancel_status=uncanceled&limit=50",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000010",
      "name": "候補の見積書を選ぶ",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        50
      ],
      "parameters": {
        "jsCode": "// The newest open quotations of this customer; one the mail names (Q-...) comes first.\nconst c = $('設定').first().json;\nif ($json.statusCode !== 200) return [{ json: { ok: false, problem: 'freee請求書の見積書を読めませんでした（HTTP ' + $json.statusCode + '）。アプリの請求書の権限と、freee請求書を使い始めているかを確かめてください。' } }];\nconst all = ($json.body.quotations || []).slice().sort((a, b) => String(b.quotation_date).localeCompare(String(a.quotation_date)) || b.id - a.id);\nif (!all.length) return [{ json: { ok: false, problem: 'このお客様には、freee に有効な見積書がないので、請求書は作っていません。' } }];\nconst named = (String(c.text).normalize('NFKC').match(/Q-?\\d{3,}/gi) || []).map((s) => s.toUpperCase());\nconst first = all.filter((q) => named.some((x) => x.replace('-', '') === String(q.quotation_number).toUpperCase().replace('-', '')));\nconst pick = [...first, ...all.filter((q) => !first.includes(q))].slice(0, Number(c.max_quotations));\nreturn pick.map((q) => ({ json: { ok: true, id: q.id } }));"
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000011",
      "name": "見積書がある？",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1760,
        50
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "ok",
              "leftValue": "={{ $json.ok }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000012",
      "name": "見積書の中身を読む",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1980,
        -50
      ],
      "parameters": {
        "url": "=https://api.freee.co.jp/iv/quotations/{{ $json.id }}?company_id={{ $('設定').first().json.freee_company_id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000013",
      "name": "お客様の請求書を読む",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2200,
        -50
      ],
      "parameters": {
        "url": "=https://api.freee.co.jp/iv/invoices?company_id={{ $('設定').first().json.freee_company_id }}&partner_ids={{ $('お客様を探す').first().json.partner.id }}&limit=100",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      },
      "executeOnce": true
    },
    {
      "id": "c8000000-0000-4000-8000-000000000014",
      "name": "AI に渡す内容を作る",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2420,
        -50
      ],
      "parameters": {
        "jsCode": "// The candidates with their lines (no prices) and the mail, for Claude.\nconst c = $('設定').first().json;\nconst qs = $('見積書の中身を読む').all().map((i) => i.json).filter((r) => r.statusCode === 200).map((r) => r.body.quotation);\nif (!qs.length) return [{ json: { ok: false, problem: 'freee の見積書を読めませんでした。' } }];\nconst inv = $json;\nif (inv.statusCode !== 200) return [{ json: { ok: false, problem: 'freee の請求書を読めませんでした（HTTP ' + inv.statusCode + ').' } }];\nconst list = qs.map((q) => q.quotation_number + ' | ' + q.subject + ' | 日付 ' + q.quotation_date + (q.expiration_date ? '、有効期限 ' + q.expiration_date : '') + '\\n' +\n  (q.lines || []).filter((l) => l.type !== 'text').map((l) => '  - ' + l.description + ': ' + Number(l.quantity) + ' ' + (l.unit || '')).join('\\n')).join('\\n\\n');\nreturn [{ json: { ok: true, quotations: qs, invoices: inv.body.invoices || [], list, text: c.text.slice(0, 8000) } }];"
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000015",
      "name": "準備できた？",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2640,
        -50
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "ok",
              "leftValue": "={{ $json.ok }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000016",
      "name": "Claude Haiku 4.5",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.5,
      "position": [
        2860,
        170
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-haiku-4-5-20251001",
          "cachedResultName": "Claude Haiku 4.5"
        },
        "options": {}
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000017",
      "name": "発注を読む（AI）",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        2860,
        -50
      ],
      "parameters": {
        "promptType": "define",
        "text": "={{ 'Open quotations of this customer (number | subject | dates, then lines with quantities):\\n' + $json.list + '\\n\\nMail from the customer:\\n' + $json.text }}",
        "messages": {
          "messageValues": [
            {
              "type": "SystemMessagePromptTemplate",
              "message": "You read a customer's mail and decide whether it places an order based on one of their quotations. You never set or mention prices.\nReturn:\n- \"is_order\": \"yes\" only if the customer clearly orders (for example \"please proceed\", \"we order as quoted\"); \"no\" if it is not an order (a question, a new request, a cancellation); otherwise \"unclear\".\n- \"order_quote\": the exact words from the mail, copied character by character, that show the order (or null).\n- \"quotation_number\": the number of the quotation the order is for, exactly as listed, or null if you cannot tell which one.\n- \"changes\": quantity changes the customer asks for: [{\"line\": \"the line description exactly as listed\", \"new_quantity\": number written in the mail, \"quote\": \"exact words from the mail with the new quantity\"}]. Do not calculate quantities.\n- \"additions\": items the customer adds that are not in the quotation: [{\"requested\": \"as written\", \"quantity\": number or null, \"unit\": \"as written or null\", \"quote\": \"exact words\"}].\n- \"reason\": one short sentence, in the language of the mail, saying why you chose \"no\" or \"unclear\" (empty for \"yes\").\nReturn only JSON: {\"is_order\": \"yes\", \"order_quote\": \"...\", \"quotation_number\": \"...\", \"changes\": [], \"additions\": [], \"reason\": \"\"}"
            }
          ]
        }
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000018",
      "name": "確かめて請求書の中身を作る",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        -50
      ],
      "parameters": {
        "jsCode": "// Code decides. Prices, tax rates and the line list come from the quotation in freee, never from the AI.\nconst p = $('AI に渡す内容を作る').first().json;\nconst c = $('設定').first().json;\nconst cust = $('お客様を探す').first().json.partner;\nconst norm = (s) => String(s ?? '').normalize('NFKC').toLowerCase().replace(/[\\s,、。「」\"'“”]/g, '');\nconst numIn = (text, q) => new RegExp('(^|[^\\\\d.])' + String(q).replace('.', '\\\\.') + '([^\\\\d.]|$)').test(String(text).normalize('NFKC').replace(/,/g, ''));\nlet a;\ntry { a = JSON.parse(String($json.text ?? '').match(/\\{[\\s\\S]*\\}/)[0]); }\ncatch (e) { return [{ json: { ok: false, problem: 'AI の答えを JSON として読めませんでした。もう一度試してください。' } }]; }\nconst text = norm(p.text);\nconst inText = (s) => norm(s).length >= 2 && text.includes(norm(s));\nconst why = a.reason ? ' AI の理由：「' + String(a.reason).slice(0, 300) + '」' : '';\nif (a.is_order === 'no') return [{ json: { ok: false, problem: 'このメールは発注ではないので、請求書は作っていません。' + why } }];\nif (a.is_order !== 'yes') return [{ json: { ok: false, problem: '何が発注されたのか、AI がはっきり読めませんでした' + (inText(a.order_quote) ? '（発注の言葉：「' + a.order_quote + '」）' : '') + '。請求書は作っていません。手で作ってください。' + why } }];\nif (!inText(a.order_quote)) return [{ json: { ok: false, problem: 'AI が発注と読んだ言葉がメールにないので、請求書は作っていません。' } }];\n// Which quotation: a number written in the mail wins; without one, only if the customer has a single open quotation\nconst named = (String(p.text).normalize('NFKC').match(/Q-?\\d{3,}/gi) || []).map((s) => s.toUpperCase().replace('-', ''));\nconst key = (q) => String(q.quotation_number).toUpperCase().replace('-', '');\nlet q = p.quotations.find((x) => x.quotation_number === a.quotation_number);\nif (named.length && !(q && named.includes(key(q)))) q = p.quotations.find((x) => named.includes(key(x)));\nif (!q) return [{ json: { ok: false, problem: 'どの見積書への発注か分かりませんでした。見積書の番号を書いてもらうか、手で請求書を作ってください。' } }];\nif (!named.length && p.quotations.length > 1) return [{ json: { ok: false, problem: 'このお客様には有効な見積書が ' + p.quotations.length + ' 件あり、メールに番号がありません（AI の推測は ' + q.quotation_number + '）。手で請求書を作るか、お客様に番号を聞いてください。' } }];\n// Already invoiced from this quotation?\nconst dup = p.invoices.find((i) => i.cancel_status !== 'canceled' && String(i.memo || '').includes(q.quotation_number));\nif (dup) return [{ json: { ok: false, problem: '見積書 ' + q.quotation_number + ' からの請求書は、もうあります（' + dup.invoice_number + '）。2枚目は作っていません。' } }];\nconst today = DateTime.now().setZone(c.timezone);\nconst warnings = [];\nif (q.expiration_date && q.expiration_date < today.toFormat('yyyy-MM-dd')) warnings.push(q.quotation_number + ' の有効期限は ' + q.expiration_date + ' で切れています。値段が今も使えるか確かめてください。');\n// Quantity changes: the line must be in the quotation, the new number must be written in the mail\nconst items = (q.lines || []).map((l) => ({ ...l }));\nconst applied = [], check = [];\nfor (const ch of Array.isArray(a.changes) ? a.changes : []) {\n  const line = items.find((l) => l.type !== 'text' && norm(l.description) === norm(ch.line)) || items.find((l) => l.type !== 'text' && norm(ch.line).length >= 2 && norm(l.description).includes(norm(ch.line)));\n  const qty = Number(ch.new_quantity);\n  if (line && Number.isFinite(qty) && qty > 0 && inText(ch.quote) && numIn(ch.quote, qty)) { applied.push({ line: line.description, from: Number(line.quantity), to: qty, quote: ch.quote }); line.quantity = qty; }\n  else check.push({ what: (ch.line || '?') + ' → ' + ch.new_quantity, why: !line ? 'この明細はない：' + q.quotation_number : !inText(ch.quote) ? '引用した言葉がメールにない' : '新しい数量がメールに書かれていない' });\n}\nfor (const ad of Array.isArray(a.additions) ? a.additions : []) {\n  if (!inText(ad.quote)) continue;\n  // The same kind of item may already be in the quotation: say so, and leave the total quantity to a person\n  const same = items.find((l) => l.type !== 'text' && norm(ad.requested).length >= 2 && norm(l.description).includes(norm(ad.requested)));\n  check.push({ what: ad.requested + (ad.quantity ? ' ' + ad.quantity + (ad.unit || '') : ''), why: same ? q.quotation_number + ' には「' + same.description + '\" (' + Number(same.quantity) + (same.unit || '') + '）があります。メールでは追加を頼まれているので、数を決めてください' : '見積書にない：' + q.quotation_number + '（値段なし）' });\n}\nconst lines = items.map((l) => l.type === 'text' ? { type: 'text', description: l.description }\n  : { type: 'item', description: l.description, quantity: Number(l.quantity), unit: l.unit || undefined, unit_price: String(l.unit_price), tax_rate: l.tax_rate, ...(l.reduced_tax_rate ? { reduced_tax_rate: true } : {}) })\n  .concat(check.map((x) => ({ type: 'text', description: ('※要確認：' + x.what + ' (' + x.why + ')').slice(0, 255) })));\n// Our own totals (tax rounded down per rate, as tax_fraction \"omit\")\nconst sub = {}; let net = 0;\nfor (const l of lines.filter((x) => x.type === 'item')) { const v = Math.round(l.quantity * Number(l.unit_price)); net += v; sub[l.tax_rate] = (sub[l.tax_rate] || 0) + v; }\nconst tax = Object.entries(sub).reduce((s, [r, v]) => s + Math.floor(v * Number(r) / 100), 0);\nconst due = c.payment_terms === 'end_of_next_month' ? today.plus({ months: 1 }).endOf('month') : today.plus({ days: Number(c.payment_days) });\nconst body = {\n  company_id: Number(c.freee_company_id), billing_date: today.toFormat('yyyy-MM-dd'), payment_date: due.toFormat('yyyy-MM-dd'), payment_type: 'transfer',\n  subject: (String(q.subject).replace(/s*(の)?(お見積(もり|り)?|御見積(り|書)?|見積(もり|り|書)?|quotation|quote)s*/gi, '').trim() || q.subject).slice(0, 255), tax_entry_method: q.tax_entry_method, tax_fraction: q.tax_fraction, withholding_tax_entry_method: q.withholding_tax_entry_method || 'out',\n  partner_id: q.partner_id, partner_title: q.partner_title || '御中',\n  memo: ('見積書 ' + q.quotation_number + ' から作成（n8n、発注メール：' + (c.from_email || c.source) + ')').slice(0, 2000), lines,\n};\nreturn [{ json: { ok: true, quotation: { number: q.quotation_number, total: q.total_amount }, customer: cust, applied, check, warnings, order_quote: a.order_quote, expected: { net, tax, total: net + tax }, body } }];"
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000019",
      "name": "請求書を作れる？",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3300,
        -50
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "ok",
              "leftValue": "={{ $json.ok }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000020",
      "name": "freee に請求書を作る",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3520,
        -150
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.freee.co.jp/iv/invoices",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000021",
      "name": "お知らせを書く",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3740,
        150
      ],
      "parameters": {
        "jsCode": "// One notice for every outcome.\nconst c = $('設定').first().json;\nconst chk = $('確かめて請求書の中身を作る').isExecuted ? $('確かめて請求書の中身を作る').all(0)[0].json : null;\nconst created = $('freee に請求書を作る').isExecuted ? $('freee に請求書を作る').all(0)[0].json : null;\nconst yen = (v) => Number(v).toLocaleString('ja-JP') + '円';\nconst lines = [];\nlet title, ok = false;\nif (created && created.statusCode === 201) {\n  const iv = created.body.invoice;\n  ok = true;\n  title = '[n8n 請求] freee に請求書の下書き：' + iv.partner_name + ' ' + yen(iv.total_amount);\n  lines.push('freee に請求書を「送付待ち」で作りました。元の見積書：' + chk.quotation.number + '。中身（納品後なら請求日も）を確かめて、freee から送ってください。', iv.report_url || '', '');\n  lines.push(iv.invoice_number + ' / ' + iv.subject + ' / ' + iv.partner_name + '（' + chk.customer.by + '）');\n  lines.push('発注：「' + chk.order_quote + '」');\n  chk.applied.forEach((x) => lines.push('変更：' + x.line + ' ' + x.from + ' → ' + x.to + '（メール：「' + x.quote + '」）'));\n  lines.push('税抜 ' + yen(iv.amount_excluding_tax) + '、消費税 ' + yen(iv.amount_tax) + '、合計 ' + yen(iv.total_amount) + '（見積書：' + yen(chk.quotation.total) + '）、期日 ' + iv.payment_date);\n  if (iv.amount_excluding_tax !== chk.expected.net || iv.amount_tax !== chk.expected.tax) lines.push('!! freee の計算した金額が、このワークフローの計算（税抜 ' + yen(chk.expected.net) + '、消費税 ' + yen(chk.expected.tax) + '）と違います。請求書を確かめてください。');\n} else {\n  title = '[n8n 請求] 請求書は作っていません';\n  if (created) lines.push('freee が請求書を受け付けませんでした（HTTP ' + created.statusCode + '): ' + JSON.stringify(created.body && (created.body.errors || created.body)).slice(0, 400));\n  else lines.push($json.problem || '原因の分からない問題です。');\n}\nif (chk && chk.warnings && chk.warnings.length) { lines.push(''); chk.warnings.forEach((w) => lines.push('!! ' + w)); }\nif (chk && chk.check && chk.check.length) { lines.push('', '要確認（請求書に、値段なしの「※要確認」の行で入れています）：'); chk.check.forEach((x) => lines.push('- ' + x.what + '：' + x.why)); }\nlines.push('', '発注のメール：' + (c.from_email || c.source) + '／' + c.subject);\nreturn [{ json: { ok, title, message: lines.join('\\n'), source: c.source, to: c.staff_email } }];"
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000022",
      "name": "依頼元に返す",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        3960,
        150
      ],
      "parameters": {
        "mode": "expression",
        "numberOutputs": 2,
        "output": "={{ $json.source === 'form' ? 0 : 1 }}"
      }
    },
    {
      "id": "c8000000-0000-4000-8000-000000000023",
      "name": "結果を表示",
      "type": "n8n-nodes-base.form",
      "typeVersion": 2.5,
      "position": [
        4180,
        50
      ],
      "parameters": {
        "operation": "completion",
        "respondWith": "text",
        "completionTitle": "={{ $json.title }}",
        "completionMessage": "={{ $json.message }}"
      },
      "webhookId": "8b4f2e6d-3a71-4c95-b0d8-7e2a5c9f1b36"
    },
    {
      "id": "c8000000-0000-4000-8000-000000000024",
      "name": "担当者に知らせる",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        4180,
        250
      ],
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $json.to }}",
        "subject": "={{ $json.title }}",
        "emailType": "text",
        "message": "={{ $json.message }}",
        "options": {
          "appendAttribution": false
        }
      }
    },
    {
      "parameters": {
        "content": "## 発注メールから、freee の請求書を作る\nお客様から「Q-0000000004 の内容で発注します。パンフレットは800部に変更で」と返信が届くと、**その見積書から、freee請求書に請求書が「送付待ち」でできます。** 明細・単価・税率は見積書からコードが写し、AI は値段を決めません。\n\n**AI（Claude）**はメールを読むだけです：発注か、どの見積書か、数量の変更は（それぞれ言葉を引用）。**コード**が確かめます：見積書があり取消されていないか、同じ見積書からもう請求書を作っていないか（社内メモ「見積書 Q-… から作成」）、新しい数量がメールに書かれているか。追加の品目やはっきりしない発注は、値段なしの「※要確認」の行にするか、請求書を作りません。\n\n### 準備\n1. freee Developers のアプリ：取引先（参照）と、[請求書] 全帳票種別（参照・更新）の権限。freee請求書を使っていること\n2. n8n の認証情報：freee は「OAuth2 API」（認可の URL https://accounts.secure.freee.co.jp/public_api/authorize 、トークンの URL .../public_api/token 、Authentication は Body）。Gmail と Anthropic も\n3. **設定** と、**発注メール** の Gmail の検索を埋める。まずフォームで試す",
        "height": 1100,
        "width": 600,
        "color": 1
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -660,
        -320
      ],
      "id": "c8000000-0000-4000-9000-000000000024",
      "name": "Overview"
    },
    {
      "parameters": {
        "content": "## 1. 発注が届く\nGmail（どのメールを発注とみなすかは、トリガーの検索で決める）か、フォーム。",
        "height": 620,
        "width": 400,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -20,
        -120
      ],
      "id": "c8000000-0000-4000-9000-000000000025",
      "name": "Section 1"
    },
    {
      "parameters": {
        "content": "## 2. お客様\nfreee に登録済みの取引先：送り主のメールアドレスか、メールに書かれた取引先の名前（1件だけ一致）。取引先は作らない。",
        "height": 620,
        "width": 840,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        420,
        -120
      ],
      "id": "c8000000-0000-4000-9000-000000000026",
      "name": "Section 2"
    },
    {
      "parameters": {
        "content": "## 3. お客様の見積書と請求書\n有効な見積書の新しいもの（メールに書かれた番号を優先）と、その明細。二重の請求を確かめるため、このお客様の請求書も読む。",
        "height": 220,
        "width": 1300,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1280,
        -320
      ],
      "id": "c8000000-0000-4000-9000-000000000027",
      "name": "Section 3"
    },
    {
      "parameters": {
        "content": "## 4. AI が読み、コードが決める\n発注か（言葉つき）、どの見積書か、数量の変更（言葉つき）。発注でない、見積書が分からない、もう請求済み、のときは止める。値段はすべて見積書から写す。",
        "height": 820,
        "width": 880,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2600,
        -320
      ],
      "id": "c8000000-0000-4000-9000-000000000028",
      "name": "Section 4"
    },
    {
      "parameters": {
        "content": "## 5. freee に請求書（送付待ち）とお知らせ\nfreee請求書に作る。期日は設定から（初期設定は翌月末）。担当者に、リンク、変更とその元の言葉、要確認、または作らなかった理由を知らせる。",
        "height": 820,
        "width": 900,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        3500,
        -320
      ],
      "id": "c8000000-0000-4000-9000-000000000029",
      "name": "Section 5"
    }
  ],
  "connections": {
    "発注メール": {
      "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": "freee の取引先を読む",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "freee の取引先を読む": {
      "main": [
        [
          {
            "node": "お客様を探す",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "お客様を探す": {
      "main": [
        [
          {
            "node": "お客様が見つかった？",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "お客様が見つかった？": {
      "main": [
        [
          {
            "node": "お客様の見積書を読む",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "お知らせを書く",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "お客様の見積書を読む": {
      "main": [
        [
          {
            "node": "候補の見積書を選ぶ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "候補の見積書を選ぶ": {
      "main": [
        [
          {
            "node": "見積書がある？",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "見積書がある？": {
      "main": [
        [
          {
            "node": "見積書の中身を読む",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "お知らせを書く",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "見積書の中身を読む": {
      "main": [
        [
          {
            "node": "お客様の請求書を読む",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "お客様の請求書を読む": {
      "main": [
        [
          {
            "node": "AI に渡す内容を作る",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI に渡す内容を作る": {
      "main": [
        [
          {
            "node": "準備できた？",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "準備できた？": {
      "main": [
        [
          {
            "node": "発注を読む（AI）",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "お知らせを書く",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Haiku 4.5": {
      "ai_languageModel": [
        [
          {
            "node": "発注を読む（AI）",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "発注を読む（AI）": {
      "main": [
        [
          {
            "node": "確かめて請求書の中身を作る",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "確かめて請求書の中身を作る": {
      "main": [
        [
          {
            "node": "請求書を作れる？",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "請求書を作れる？": {
      "main": [
        [
          {
            "node": "freee に請求書を作る",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "お知らせを書く",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "freee に請求書を作る": {
      "main": [
        [
          {
            "node": "お知らせを書く",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "お知らせを書く": {
      "main": [
        [
          {
            "node": "依頼元に返す",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "依頼元に返す": {
      "main": [
        [
          {
            "node": "結果を表示",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "担当者に知らせる",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}