RECIPE 037
영업 성과 개인별 순위 리포트
- ▸HTTP Request 또는 Webhook으로 JSON 데이터를 수신할 수 있다
- ▸Code 노드로 JSON을 평탄화(flatten)해 표 형태로 변환할 수 있다
- ▸변환된 데이터를 Google Sheets 또는 Excel 파일로 저장할 수 있다
✅사전 준비
- □변환할 JSON 데이터 구조 파악 (중첩 여부 확인)
- □Google Sheets 자격증명 연결 완료
- □출력 컬럼 구조 확정
중첩 JSON 평탄화 — Code 노드
function flatten(obj, prefix = '') {
return Object.entries(obj).reduce((acc, [key, val]) => {
const newKey = prefix ? `${prefix}_${key}` : key;
if (val && typeof val === 'object' && !Array.isArray(val)) {
Object.assign(acc, flatten(val, newKey));
} else if (Array.isArray(val)) {
acc[newKey] = val.join(', ');
} else {
acc[newKey] = val;
}
return acc;
}, {});
}
const items = $input.all();
return items.map(({ json }) => ({ json: flatten(json) }));Spreadsheet File 노드 — Excel 저장 설정
Operation: Write
File Format: XLSX
File Name: export_{{ $now.toFormat('yyyyMMdd_HHmm') }}.xlsx
Sheet Name: 데이터
Header Row: true💡 TIPJSON 배열이 루트에 있는 경우 n8n이 자동으로 각 항목을 개별 아이템으로 펼쳐줍니다. 배열이 중첩된 경우엔 Code 노드에서 먼저 items.flatMap() 으로 풀어낸 뒤 평탄화하세요.
⚡
워크플로우 JSON
아래 JSON을 복사해 n8n에 바로 임포트하세요
📥 n8n 임포트 방법
n8n 화면 우측 상단 메뉴 (⋮) → Import from JSON → 아래 JSON 전체 복사 후 붙여넣기 → Import 클릭
n8n 화면 우측 상단 메뉴 (⋮) → Import from JSON → 아래 JSON 전체 복사 후 붙여넣기 → Import 클릭
{
"recipeId": "recipe037",
"title": "영업 성과 개인별 순위 리포트",
"chapter": 3,
"category": "영업 & 리드 관리 자동화",
"difficulty": "⭐⭐",
"estimatedTime": "35분",
"description": "매주 금요일 오후 5시 HubSpot에서 이번 주 완료된 딜을 담당자별로 집계하고 실적 순서로 정렬하여 Slack 팀 채널에 순위표를 발송합니다.",
"apps": [
"HubSpot",
"Slack"
],
"nodes": [
{
"id": "trigger_schedule",
"type": "trigger",
"name": "Schedule Trigger - 매주 금요일 오후 5시",
"service": "scheduleTrigger",
"config": {
"rule": "0 17 * * 5"
}
},
{
"id": "hubspot_deals",
"type": "action",
"name": "HubSpot - 이번 주 완료 딜 조회",
"service": "hubspot",
"config": {
"operation": "getDeals",
"filters": [
{
"propertyName": "dealstage",
"operator": "EQ",
"value": "closedwon"
},
{
"propertyName": "closedate",
"operator": "THIS_WEEK"
}
],
"properties": [
"dealname",
"amount",
"hubspot_owner_id",
"closedate"
]
}
},
{
"id": "function_rank",
"type": "function",
"name": "Function - 담당자별 집계 및 정렬",
"code": "const deals = $input.all();\nconst ownerMap = {};\ndeals.forEach(d => {\n const owner = d.json.hubspot_owner_id || '미배정';\n if (!ownerMap[owner]) ownerMap[owner] = { count: 0, total: 0 };\n ownerMap[owner].count++;\n ownerMap[owner].total += Number(d.json.amount || 0);\n});\nconst sorted = Object.entries(ownerMap)\n .map(([owner, v]) => ({ owner, ...v }))\n .sort((a, b) => b.total - a.total);\nreturn sorted.map((item, i) => ({ json: { rank: i + 1, ...item } }));"
},
{
"id": "slack_ranking",
"type": "action",
"name": "Slack - 순위 리포트 발송",
"service": "slack",
"config": {
"channel": "={{ $env.SLACK_SALES_CHANNEL }}",
"message": "🏆 이번 주 영업 실적 순위\n{{ $items().map((item, i) => `${i+1}위 ${item.json.owner} — ${item.json.count}건 / ₩${item.json.total.toLocaleString()}`).join('\\n') }}"
}
}
],
"connections": [
{
"from": "trigger_schedule",
"to": "hubspot_deals"
},
{
"from": "hubspot_deals",
"to": "function_rank"
},
{
"from": "function_rank",
"to": "slack_ranking"
}
],
"envVars": [
{
"key": "SLACK_SALES_CHANNEL",
"description": "영업팀 Slack 채널 ID"
}
],
"qrCodeUrl": "https://www.koreaaitimes.com/books/n8n-vol2/recipe037"
}