주문 0건과 금액 0원인 주문은 다른 결과로 남겨야 합니다. 기존 예제는 앞 노드가 빈 배열을 반환하면 집계 노드가 실행되지 않았습니다. 이번 수정은 배열 전체를 한 개의 제어 항목에 담아, 빈 입력도 EMPTY 결과 한 건으로 남깁니다.
| 입력 | 실제 결과 | 전송 판단 |
|---|---|---|
| 기존 방식: 항목 0개 | 집계 미실행 · 출력 없음 | 판단할 결과 없음 |
| 수정 방식: rows=[] | EMPTY · 합계 0 · 정상 0건 | false |
| 금액 0인 paid 주문 1개 | READY · 합계 0 · 정상 1건 | true |
| 빈 문자열 금액 | REVIEW · invalid_amount | false |
| 같은 ID에 100과 200 | REVIEW · 부분 합계 100 | false |
| 같은 ID·100 두 번 | READY · 합계 100 · 중복 1 | true |
| 취소 주문만 1개 | NO_PAID_ORDERS · 합계 0 | false |
2026년 10월 5일 n8n 2.40.7 CLI에서 비활성 워크플로를 실제로 가져와 일곱 조건을 한 번씩 실행했습니다. 네트워크가 없는 새 Docker 컨테이너와 가상 자료를 사용했습니다. 표의 true는 이 예제의 집계 규칙 통과이며 회계 확정이나 실제 발송 완료를 뜻하지 않습니다.
한 개의 항목에 빈 배열을 담는 것이 핵심입니다
기존: Sample Orders → [] → Validate and Sum 미실행
수정: Sample Orders → [{json:{rows:[]}}] → EMPTY 결과 1개이를 기존 연결의 끝에 Code 노드 하나만 붙여 해결할 수는 없습니다. 앞 단계가 이미 항목 0개로 멈추면 그 Code 노드에도 도달하지 않습니다. 자료를 가져오는 경계에서 빈 결과를 포함한 한 개의 제어 항목을 만들어야 합니다. 이번 재현은 그 경계를 Sample Orders로 대체했습니다. 실제 CSV·DB·HTTP 커넥터 연결은 별도 시험 대상입니다.
n8n에서 그대로 시험하는 방법
- 운영 흐름을 수정하지 말고 새 수동 워크플로에 Manual Trigger → Code → Code를 만듭니다.
- 두 Code 노드 모두 JavaScript, Run Once for All Items로 둡니다. 첫 노드 이름은 Sample Orders, 둘째는 Validate and Sum으로 정합니다.
- 아래 첫 코드와 집계 코드를 각각 붙여 넣고 실행합니다. 마지막 출력 한 개의 result_state와 ready_to_send를 확인합니다.
- rows를 0원 주문으로 교체해 다시 실행합니다. 둘 다 합계는 0이지만 EMPTY/READY와 정상 건수가 달라야 합니다.
// Sample Orders: 빈 배치도 제어 항목 1개로 전달합니다.
return [{json:{rows:[]}}];
// 0원 주문 시험 때는 위 return을 아래 내용으로 교체합니다.
// return [{json:{rows:[{order_id:"0001",status:"paid",amount:0}]}}];Validate and Sum 전체 코드 펼치기
// Preview only: no mail, file write, HTTP request, or persistent deduplication.
// Run in Code / JavaScript / Run Once for All Items.
// One control item carries the whole batch, including an empty batch.
const envelope = $input.first().json;
if (!Array.isArray(envelope.rows)) throw new Error('rows must be an array');
const rows = envelope.rows;
const accepted = [], issues = [], cancelled = [], duplicates = [];
const seen = new Map();
for (let i = 0; i < rows.length; i++) {
const row = rows[i], source_row = i + 1;
// IDs must already be strings: converting a number cannot restore leading zeroes.
if (typeof row.order_id !== 'string' || !row.order_id.trim()) {
issues.push({source_row, reason: 'missing_or_nonstring_id'}); continue;
}
const id = row.order_id.trim();
if (row.status === 'cancelled') { cancelled.push({source_row, order_id:id}); continue; }
if (row.status !== 'paid') { issues.push({source_row, order_id:id, reason:'unknown_status'}); continue; }
// Empty/null must not become zero. This example accepts nonnegative integer KRW.
const raw = row.amount;
const validNumber = typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0;
const validText = typeof raw === 'string' && /^(?:\d+|\d{1,3}(?:,\d{3})+)$/.test(raw.trim());
const amount = validNumber ? raw : validText ? Number(raw.replaceAll(',','').trim()) : NaN;
if (!Number.isSafeInteger(amount) || amount < 0) {
issues.push({source_row, order_id:id, reason:'invalid_amount'}); continue;
}
if (seen.has(id)) {
// Conflicting duplicates block use of the preview; neither total is authoritative.
if (seen.get(id) !== amount) issues.push({source_row, order_id:id, reason:'conflicting_duplicate'});
else duplicates.push({source_row, order_id:id});
continue;
}
seen.set(id, amount); accepted.push({source_row, order_id:id, amount});
}
const total = accepted.reduce((sum,row)=>sum+row.amount,0);
if (!Number.isSafeInteger(total)) throw new Error('Unsafe integer total');
return [{json:{input_count:rows.length, accepted_count:accepted.length,
total_krw:total, accepted, issues, cancelled, duplicates,
result_state:rows.length===0?'EMPTY':issues.length?'REVIEW':accepted.length?'READY':'NO_PAID_ORDERS',
// Empty input needs review too; success must not mean there was data.
ready_to_send:rows.length>0 && accepted.length>0 && issues.length===0}}];오류 합계를 확정 매출로 보내지 마세요
같은 ID에 100원과 200원이 있으면 예제는 첫 100원을 부분 합계에 남기고 REVIEW로 표시합니다. 어느 금액이 옳은지는 결정하지 않습니다. 후속 노드는 ready_to_send가 명시적으로 true인 경우에만 연결하고, 충돌은 원자료로 확인하세요. 주문 ID는 문자열이어야 앞의 0이 보존됩니다. 빈 문자열·null·음수·소수·잘못된 쉼표는 금액으로 받지 않습니다.
취소는 “보고 대상에서 제외된 행”으로만 취급합니다. 같은 주문의 결제·취소 이력을 시간순으로 대사하는 장부가 아니며, 환불·여러 통화·소수 금액에는 적용하지 마세요. 중복 검사는 한 배치 안에서만 작동합니다. 다음 실행의 재전송 방지는 별도 지속 저장 계약이 필요합니다.
정정: 이전 원고는 빈 입력용 조건문이 있으면 결과가 나오는 것처럼 설명했으나 실제 엔진에서는 그 노드가 실행되지 않았습니다. 이번에는 실패 대조군과 수정한 실행을 함께 확인했습니다. 자료: n8n Understand n8n’s data structure / Using the Code node, 문서 확인 2026-10-05; 자체 CLI 실행 기록.
관련 실험: 재시도와 중복 저장 · 메모 길이의 세 기준
작성·실험 정리: HEYDLIFE 운영자와 AI 작성 보조. 개정: 2026년 10월 5일. 가상 자료로 수행한 시험과 실제 서비스 사용 성과를 구분했습니다.