영수증 처리 자동화 완성기 - 부가세 신고 기간마다 오는 고통 탈피를 위해! (수정판)

다른 유형의 유전자를 보여주는 일련의 다른 색상 다이어그램


5개 사업자등록증을 갖고 있다 보니, 반기마다 찾아오는 부가세 신고가 진짜 싫습니다.

부가세 신고 때마다 반복되는 악몽:

📧 세무사: "6개월치 온라인 결제 내역 보내드려요. 각각 어떤 비용인지 분류해주세요"

📱 나: 토스페이먼츠, 네이버파이넨셜, 한국정보통신(주)... 수백 건 내역 보며 기억 더듬기

🧠 "이건 뭐 샀더라? 비품? 재료? 집기?"

😱 6개월 전 구매 내역은 당연히 기억 안 남

왜 이게 문제였냐면:

오프라인 결제: 세무사가 알아서 처리
온라인 결제: "한국정보통신(주) 25,000원" 이게 끝 : 내역을 알 수가 없음.

진짜 뭘 샀는지 알 길이 없거든요.

그래서 이 업무를 보완하고 직원들의 업무 효율을 위해 자동화했습니다.

사용한 도구 선택 고민:

처음에는 "최신 AI 써서 완벽하게!" 생각했는데, 현실적으로 비용 계산해보니...

방법

월 무료 한도

초과 시 비용

복잡도

Gemini 1.5 Flash

1,500장

5-10원/장

간단

Google Vision + GPT-3.5

1,000장

1-2원/장

복잡

GPT-4o mini

없음

10-20원/장

간단

  • Google Vision: 월 1,000건 무료 → 5개 매장 합쳐도 충분

  • GPT-3.5 Turbo: 장당 1-2원, 어차피 스크린샷이라 충분

월 예상 비용 1,000원 이하로 시스템 구축 결정!

워크플로우:

직원이 온라인 구매 → 영수증 스크린샷 촬영
↓
매장별 구글 드라이브 폴더 업로드
↓
Google Vision이 텍스트 추출
↓
GPT-3.5가 품목 분석 후 계정 자동 분류
↓
매장별 구글 시트에 자동 기록
↓
인식하기 좋게 파일명 변경 후 처리완료 폴더로 이동

프로세스의 흐름을 보여주는 다이어그램

각 노드별 역할과 선택 이유

1. Google Drive Trigger

  • 역할: 매장 폴더에 새 이미지 업로드 감지

  • 결정: 매장별 5개 워크플로우 따로하자. → 관리 편의성 + 오류 격리

2. Google Drive Download

  • 역할: 업로드된 영수증 이미지 파일 다운로드

  • 결정: Google Drive 노드 → 자동 바이너리 처리

3. Code 노드 (클로드에게 물어봄)

  • 역할: 바이너리 이미지 데이터를 Base64로 변환

  • 왜 필요했나: n8n의 바이너리 데이터가 파일시스템 참조(filesystem-v2)로 저장됨

  • 해결: getBinaryDataBuffer()toString('base64') 직접 변환

// 실제 바이너리 데이터 읽어서 Base64 변환
const binaryData = await this.helpers.getBinaryDataBuffer(0, 'data');
const base64Data = binaryData.toString('base64');

4. Google Vision OCR

  • 역할: 영수증 이미지에서 텍스트 추출

  • 결정: Google Vision → 월 1,000건 무료

  • 설정: HTTP Request로 직접 API 호출 (n8n 전용 노드 없음)

5. Basic LLM Chain

  • 역할: OCR 텍스트를 구조화된 데이터로 변환

  • 결정: LLM Chain → 프롬프트 템플릿 관리 용이 + 변수 처리 깔끔

  • 핵심: 계정 분류 규칙을 상세하게 정의

계정 분류 기준:
- 비품: 사무용품, 청소용품, 소모품
- 집기: 가구, 전자제품, 조명  
- 재료: 카페 원재료, 포장재
- 식대: 식음료, 배달음식
...

6. Set 노드 (JSON 파싱)

  • 역할: LLM이 생성한 JSON 문자열을 실제 객체로 변환

  • 왜 필요했나: LLM Chain 출력이 {text: "JSON문자열"} 형태

  • 결정: Set 노드 → JSON.parse() 간단 처리

// LLM 출력 텍스트를 실제 JSON 객체로 변환
parsed_data: {{ JSON.parse($json.text) }}

7. Google Sheets (병렬 처리)

  • 역할: 파싱된 데이터를 매장별 시트에 기록

  • 결정: Append → 영수증은 항상 새로 추가 (중복 체크 불필요)

8. Update File → Move File (순차 처리)

  • 역할: 파일명 변경 후 처리완료 폴더로 이동

  • 결정: 순차적 → Update 먼저, Move 나중 (논리적 순서)

네이버페이.png → 2025-07-22_모타바스켓.png → 처리완료 폴더

가장 고민했던 부분: AI 계정 분류

세무사가 매번 물어보는 그 질문을 AI가 알아서 답하게 하는 게 핵심이었어요.

처음 AI 분류 결과:

{
  "account": "기타",
  "items": "상품"
}

뭐든지 "기타"로 분류하는 AI...

계속된 프롬프트 개선:

  • 품목 20글자 이하 요약 규칙

  • 9개 계정 카테고리별 구체적 예시

  • LLM을 이용한 데이터 검증: LLM 프롬프트에 "추출된 정보가 확실하지 않으면 memo 필드에 '확인 필요' 라고 적어줘" 와 같은 규칙을 추가. 나중에 구글 시트에서 '확인 필요'가 포함된 행만 필터링하여 수동으로 검토할 수 있습니다.

  • "기타" 사용 최소화 지침

최종 결과:

{
  "date": "2025-06-10",
  "store": "N pay",
  "items": "모타 실버 와이어 바스켓",
  "total": "111150",
  "payment": "카드",
  "account": "비품",
  "memo": "무이자 6개월"
}

네이버페이 복잡한 영수증도 파싱 성공!

실무 최적화 고민들

파일 관리 전략

고민: 처리된 영수증을 어떻게 관리할까? 해결: 원본 폴더는 깔끔하게, 처리완료는 따로 보관

더불어, 항상 정리되지 않은 스크린샷 네이밍들도 정리하기 좋게 이름 변환해서 이동!!

📁 사무실/
  ├── 📄 새로운 영수증들 (처리 대기)
  └── 📁 처리완료/
      └── 📄 2025-07-22_모타바스켓.png

월별 데이터 관리 고민

A안: 월별 시트 자동 생성 → 복잡한 워크플로우 B안: 고정 시트 + 날짜 컬럼 관리 → 단순한 구조

결정: B안 선택 → 현재는 단순하게, 나중에 월별 리포트 자동화 별도 구축 (앞으로 할 일)

5개 매장 완전 연동

각 매장별로 독립된 시스템 구축:

  • 동교동, 연남동, 인사동, 구로, 사무실

  • 매장별 독립 폴더 + 독립 시트 + 동일 워크플로우

  • 한 매장 문제 시 다른 매장 영향 없음

최종 완성된 자동화

이제 직원들이 할 일:

  • 온라인 구매 후: 영수증 스크린샷 → 해당 매장 폴더 업로드

  • 끝.

느낀 점

비용 효율성을 고민했다. 월 1,000원으로 5개 매장 영수증 처리 자동화를 완성한다면 괜찮은듯.

AI가 세무 업무까지 해주는 날까지. "이건 비품인지 집기인지 재료인지" 이런 세무사의 파일을 AI가 매칭까지 알아서 완벽히 하는 그날까지 자동화 공부를...

루틴만 잘 잡으면 직원들도 편하다. 복잡한 분류나 정리, 기록은 다 자동화하고, 직원들은 그냥 사진만 찍어서 올리면 끝.(나면 좋겠다).

그냥... 또 해봤다는 기록. ☕


수정본 - 영수증 자동화 워크플로우 - 완성까지의 여정

기계 학습 시스템의 프로세스를 보여주는 다이어그램

🚀 수정 개요

목표: 5개 매장의 영수증을 자동으로 OCR 처리 → Google Sheets 정리 → 파일 관리 핵심 기술: n8n + Google Vision API + GPT-3.5 + Google Sheets + Notion


📋 1단계: 초기 설계 (이론적 완성)

✅ 처음 계획한 워크플로우

Google Drive Trigger → 파일 업로드 감지 → OCR → GPT → Sheets 기록

설계 내용:

  • Google Drive Trigger: 폴더에 파일 업로드되면 즉시 실행

  • Google Vision OCR: 영수증 텍스트 추출

  • GPT-3.5: 텍스트를 구조화된 JSON으로 변환

  • Google Sheets: 7개 컬럼에 자동 기록 (날짜|구매처|품목|총액|결제수단|계정|비고)

  • 파일 정리: 처리 완료 후 이름 변경 & 폴더 이동

초기 성과:

  • ✅ 기본 워크플로우 로직 완성

  • ✅ 데이터 구조 설계 완료

  • ✅ Notion 대시보드 연동

  • ✅ 비용 최적화 (월 1,000원 이하)


❌ 2단계: 첫 번째 큰 벽 - 파일 없을 때 시스템 중단

🔥 치명적 문제 발견

상황: 밤에 영수증 폴더가 비어있음
Google Drive Trigger 실행 → 파일 없음 → 에러 발생 → 워크플로우 전체 중단
결과: 다음날 직원이 영수증 업로드해도 처리 안됨

왜 문제였나:

  • Google Drive Trigger는 폴더에 파일이 있어야만 정상 작동

  • 파일이 없으면 "No data" 에러로 전체 워크플로우 중단

  • 한 번 중단되면 수동으로 다시 시작해야 함

🛠️ 해결책: Schedule + Search 방식 도입

기존 방식:

Google Drive Trigger (파일 감지) → 즉시 처리
문제: 파일 없으면 중단

개선된 방식:

Schedule Trigger (10분마다) → Google Drive Search (폴더 검색) → 파일 있으면 처리
장점: 파일 없어도 정상 동작

핵심 개선:

// Google Drive Search 쿼리
('1l7Y4PWlT1BaY---------------' in parents) 
and (name contains '.jpg' or name contains '.jpeg' or name contains '.png')

⚡ 3단계: 두 번째 큰 벽 - 여러 파일 동시 처리 문제

🔥 새로운 문제 발견

상황: 직원이 영수증 5장을 한꺼번에 업로드
결과: 
- 첫 번째 파일만 처리됨
- 나머지 파일들 에러 발생
- "Paired item data unavailable" 오류 연발

왜 문제였나:

  1. 파일 정보 손실: 워크플로우 중간에 원본 파일 ID/이름이 사라짐

  2. 잘못된 참조: $('Download file').item.json.id 같은 참조가 여러 파일 처리 시 꼬임

  3. 데이터 흐름 단절: 각 노드에서 파일 정보를 제대로 전달하지 못함

🛠️ 해결책: 파일 정보 전달 체인 구축

문제가 된 노드별 분석:

1) Code 노드 (Base64 변환)

// 기존: 파일 정보 누락
return [{
  json: {
    image_base64: base64Data,
    file_name: $binary.data.fileName,
    file_size: $binary.data.fileSize
  }
}];

// 개선: 원본 파일 정보 추가
return [{
  json: {
    image_base64: base64Data,
    file_name: $binary.data.fileName,
    file_size: $binary.data.fileSize,
    // 핵심 추가: 원본 파일 정보 보존
    original_file_id: $input.item.json.id,
    original_file_name: $input.item.json.name
  }
}];

2) JSON 데이터 추출 노드

// 기존: 파일 정보 참조 실패
{
  "name": "parsed_data",
  "objectValue": "={{ JSON.parse($json.text) }}"
}

// 개선: 안정적 파일 정보 참조
{
  "name": "parsed_data",
  "objectValue": "={{ $json.text }}"  // GPT 응답
},
{
  "name": "file_id",
  "stringValue": "={{ $('Code').item.json.original_file_id }}"
},
{
  "name": "file_name", 
  "stringValue": "={{ $('Code').item.json.original_file_name }}"
}

3) Update file & Move file 노드

// 기존: 잘못된 참조
"fileId": "={{ $('Download file').item.json.id }}"  // 에러 발생

// 개선: 안정적 참조
"fileId": "={{ $json.file_id }}"  // JSON 추출에서 전달받은 ID 사용

🎯 4단계: 세 번째 큰 벽 - GPT 응답 구조 문제

🔥 JSON 파싱

상황: GPT가 이상한 형태로 응답
- "파일ID", "파일명" 텍스트 출력
- JSON 구조가 계속 바뀜
- 파싱 에러 연속 발생

복잡했던 GPT 프롬프트:

응답 형식:
{
  "parsed_data": { 영수증 데이터 },
  "file_info": {
    "id": "실제 파일 ID",
    "name": "실제 파일명"
  }
}

🛠️ 해결책: 단순화와 분리

GPT 역할 단순화:

// 기존: 복잡한 중첩 구조 요구
file_info까지 GPT가 처리하려고 함

// 개선: GPT는 영수증 데이터만
{
  "date": "YYYY-MM-DD",
  "store": "매장명", 
  "items": "구매 품목",
  "total": "총액",
  "payment": "결제수단",
  "account": "계정",
  "memo": "메모"
}

파일 정보는 별도 관리:

  • GPT: 영수증 데이터만 처리

  • Code 노드: 파일 정보 보존

  • JSON 추출: 두 정보를 합쳐서 전달


🏆 5단계: 최종 완성 - 안정성 달성

💎 최종 워크플로우 구조

Schedule Trigger (10분마다)
↓
Google Drive Search (폴더 내 영수증 검색)
↓
IF 노드 (파일 있을 때만 진행)
↓
Download file (파일 다운로드)
↓
Code (Base64 변환 + 파일 정보 보존)
↓
Google Vision OCR (텍스트 추출)
↓
Basic LLM Chain (GPT-3.5 구조화)
↓
JSON 데이터 추출 (영수증 데이터 + 파일 정보 통합)
↓
병렬 처리:
  ├─ Google Sheets 기록
  ├─ Update file (파일명 변경)
  └─ Move file (폴더 이동)

🔧 핵심 개선사항 요약

1) 안정성

// 기존: Google Drive Trigger (불안정)
파일 없음 → 에러 → 시스템 중단

// 개선: Schedule + Search (안정)
파일 없음 → 정상 종료 → 10분 후 다시 체크

2) 다중 파일 처리

// 기존: 첫 번째 파일만 처리
여러 파일 → 참조 오류 → 일부만 처리

// 개선: 각 파일 독립 처리  
여러 파일 → 각각 별도 체인 → 모두 완벽 처리

3) 파일 정보 전달

// 기존: 중간에 파일 정보 손실
Google Drive → ... → Update file (ID 찾을 수 없음)

// 개선: 끝까지 파일 정보 보존
Google Drive → Code → JSON추출 → Update file (완벽 연결)

4) 중복 처리 방지

처리 전: 원본폴더/영수증.jpg
처리 후: 완료폴더/2025-01-15_스타벅스.jpg

다음 검색 시: 원본폴더에서 해당 파일 없음 → 중복 처리 방지

📊 최종 성과

🎯 기술적 성취

  • 안정성

  • 많은 파일 동시 처리: 여러 파일 업로드해도 모두 처리

  • 완전 자동화: 업로드만 하면 모든 과정 자동 완료

💰 비즈니스 가치

  • 인건비 절약: 직원들의 영수증 정리 리소스 절약 + 계정 세팅

  • 실수 제거: 수동 입력 오류 100% 방지

  • 실시간 관리: 언제든 매장별 지출 현황 파악

  • 확장성: 새 매장 추가 시 15분 내 설정 완료

다른 숫자를 가진 다른 색상 다이어그램 세트

[다음 편: 월별 리포트 자동화 후 노션 적재 목표 →]

17
10개의 답글

👉 이 게시글도 읽어보세요