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" 오류 연발
왜 문제였나:
파일 정보 손실: 워크플로우 중간에 원본 파일 ID/이름이 사라짐
잘못된 참조:
$('Download file').item.json.id
같은 참조가 여러 파일 처리 시 꼬임데이터 흐름 단절: 각 노드에서 파일 정보를 제대로 전달하지 못함
🛠️ 해결책: 파일 정보 전달 체인 구축
문제가 된 노드별 분석:
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분 내 설정 완료
[다음 편: 월별 리포트 자동화 후 노션 적재 목표 →]