HEYDLIFE / n8n 자동화

n8n 주문 0건이 사라지는 문제: 빈 입력과 0원 주문을 구분하는 수정 예제

실제 n8n 실행에서 빈 배열은 집계 노드에 도달하지 않았습니다. 빈 입력·0원·누락·중복·충돌을 구분하는 코드와 일곱 조건의 결과를 제공합니다.

주문 0건과 금액 0원인 주문은 다른 결과로 남겨야 합니다. 기존 예제는 앞 노드가 빈 배열을 반환하면 집계 노드가 실행되지 않았습니다. 이번 수정은 배열 전체를 한 개의 제어 항목에 담아, 빈 입력도 EMPTY 결과 한 건으로 남깁니다.

입력실제 결과전송 판단
기존 방식: 항목 0개집계 미실행 · 출력 없음판단할 결과 없음
수정 방식: rows=[]EMPTY · 합계 0 · 정상 0건false
금액 0인 paid 주문 1개READY · 합계 0 · 정상 1건true
빈 문자열 금액REVIEW · invalid_amountfalse
같은 ID에 100과 200REVIEW · 부분 합계 100false
같은 ID·100 두 번READY · 합계 100 · 중복 1true
취소 주문만 1개NO_PAID_ORDERS · 합계 0false

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에서 그대로 시험하는 방법

  1. 운영 흐름을 수정하지 말고 새 수동 워크플로에 Manual Trigger → Code → Code를 만듭니다.
  2. 두 Code 노드 모두 JavaScript, Run Once for All Items로 둡니다. 첫 노드 이름은 Sample Orders, 둘째는 Validate and Sum으로 정합니다.
  3. 아래 첫 코드와 집계 코드를 각각 붙여 넣고 실행합니다. 마지막 출력 한 개의 result_state와 ready_to_send를 확인합니다.
  4. 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일. 가상 자료로 수행한 시험과 실제 서비스 사용 성과를 구분했습니다.