한 줄로 시작했다

docs의 문서, 프로그램, 엑셀을 분석해줘

AICassa는 이 한 줄에서 시작했다. docs 폴더에는 국세청이 무료로 배포하는 간편장부 작성 프로그램(엑셀+VBA), 62쪽짜리 사용자 설명서 PDF, 국세청 표준 간편장부 엑셀 서식이 들어 있었다. 화면 기획서도, 요구사항 문서도 없었다.

그래도 충분했다. 이미 수많은 영세사업자가 쓰고 있는 프로그램이 있다는 건, 요구사항이 이미 검증된 형태로 존재한다는 뜻이다. 새로 상상할 필요 없이, 있는 것을 정확히 읽어내면 된다.

이 글은 그 세 가지 자료에서 요구사항을 뽑아내 CodeIgniter 4 기반 웹 ERP로 다시 설계한 과정이다. 클로드코드와 함께 진행했고, 결과물은 간이·영세사업자를 위한 간편장부 SaaS인 AICassa(아이카사)가 됐다. 이름은 이탈리아어로 '금고, 현금 출납'을 뜻하는 cassa에서 따왔다.

세 가지 자료, 세 가지 역할

자료마다 뽑아낼 것이 달랐다. 처음 분석에서 이 구분을 먼저 해둔 것이 이후 설계를 단순하게 만들었다.

자료

정체

여기서 뽑은 것

간편장부 작성 프로그램 v3.4 (.xls)

엑셀+VBA 프로그램, 17개 시트

데이터 모델, 계산 로직, 신고서식

사용자 설명서 (PDF, 62쪽)

기능·화면·업무흐름 매뉴얼

기능 명세(요구사항)

간편장부 엑셀 서식 (.xlsx)

국세청 표준 장부 서식

최종 장부 출력 레이아웃

설명서는 "무엇을 해야 하는가"를, 프로그램은 "어떻게 계산하는가"를, 표준 서식은 "결과가 어떻게 생겨야 하는가"를 알려줬다. 설명서 목차가 그대로 메뉴 구조가 됐고, 프로그램 시트가 그대로 테이블 후보가 됐다.

왜 웹으로 옮기는가

국세청 프로그램은 잘 만든 도구다. 증빙유형만 고르면 부가세가 자동 계산되고, 장부를 다 쓰면 종합소득세 신고서식까지 채워준다. 문제는 그 도구가 놓인 자리였다.

  • Windows + MS Excel 전용이다. 맥, Microsoft 365 웹, 모바일에서는 쓸 수 없다.
  • 매크로(VBA) 보안 차단을 풀어야 돌아간다. 회계를 모르는 사장님에게 "매크로 사용" 버튼은 첫 번째 벽이다.
  • 파일 하나가 곧 사용자 하나다. 백업, 공유, 버전 관리가 모두 사용자 몫이다.
  • 세법과 서식이 매년 바뀔 때마다 새 파일을 다시 받아야 한다.

웹 ERP의 명분은 여기서 나왔다. 기능을 새로 발명하는 게 아니라, 같은 기능을 더 많은 사람이 덜 고생하며 쓰게 하는 것이다. 목표도 그에 맞춰 잡았다.

  1. 브라우저만 있으면 되는 크로스플랫폼
  2. 로그인 기반 다중 사용자, 사용자별 다중 사업장·다중 소득
  3. 입력 → 집계(영업현황표) → 종합소득세 신고서식 자동 생성 파이프라인 유지
  4. 세법·서식 갱신은 서버 배포 한 번으로 일괄 반영

엑셀 시트를 테이블로 역설계하기

먼저 제도부터 이해해야 했다. 간편장부는 회계지식 없는 영세사업자용 장부다. 직전연도 수입금액이 도·소매업 3억원, 제조·음식·건설업 1.5억원, 부동산임대·서비스업 7,500만원 미만이면 대상이고, 전문직은 수입과 무관하게 제외된다. 사업장이 둘이면 장부도 둘, 소득 종류가 둘이어도 장부는 둘이다. 이 규칙 하나가 데이터 모델의 뼈대를 정했다.

장부 한 줄은 국세청 서식 기준 8개 열이다.

① 일자 · ② 계정과목 · ③ 거래내용 · ④ 거래처
⑤ 수입(금액/부가세) · ⑥ 비용(금액/부가세)
⑦ 사업용 유·무형자산 증감(금액/부가세) · ⑧ 비고(증빙)

프로그램의 시트들은 이렇게 테이블로 옮겼다.

엑셀 시트

웹 ERP 테이블

비고

정보 (계정과목 목록)

accounts

수입 2종, 비용 19종, 자산 5종. 시드 데이터

대량등록_거래처

partners

사업자번호 중복 불가

장부 입력 영역

ledger_entries

핵심 거래 테이블, 귀속연도 포함

내자산관리

assets

취득·처분, 상각방법, 내용연수, 누계액

내용연수별상각률

depreciation_rates

시드, 적용연도 컬럼 추가

표준산업분류연계표

industry_codes

업종코드 ↔ 내용연수, 적용연도 컬럼 추가

(없음, 사용자 정보)

businesses

사업장 = 테넌트 하위 단위

엑셀에는 없던 것도 생겼다. 상각률과 업종코드 표에 effective_year를 붙인 것이 그 예다. 엑셀은 매년 파일을 새로 받으니 연도 개념이 필요 없지만, 웹은 한 서버에서 여러 귀속연도를 다뤄야 한다. 파일 버전이 하던 일을 컬럼이 대신 맡은 셈이다.

가장 중요한 발견은 증빙유형(비고)이 부가세 계산의 키라는 점이었다. 사용자는 부가세를 직접 입력하지 않는다. 공급가액과 증빙유형(세금계산서·계산서·신용카드·현금영수증·간이영수증·기타)만 고르면 프로그램이 부가세를 계산한다. 원본 프로그램의 핵심 자동화가 바로 이것이었고, 웹에서도 반드시 지켜야 할 규칙이 됐다.

VBA를 서비스 계층으로 옮기기

세무 프로그램에서 "대충 비슷하게"는 통하지 않는다. 부가세 자동계산, 감가상각 한도, 소득금액 계산식은 국세청 로직과 정확히 같아야 한다. 그래서 설계 문서를 쓴 다음 바로 코드로 가지 않고, 원본을 더 깊이 파는 단계를 따로 뒀다.

  1. VBA 전체 덤프: 프로그램 안의 매크로 코드를 모두 텍스트로 뽑아냈다 (간편장부_VBA전체덤프.txt).
  2. 계산식 명세화: 셀 수식과 VBA에 흩어진 계산 규칙을 한 문서로 정리했다 (간편장부_계산식명세.md).
  3. 신고서식 대조 검증: 웹이 만든 서식을 원본 프로그램 결과와 항목별로 맞춰봤다 (신고서식_대조_검증.md).

이렇게 정리한 규칙은 CI4 서비스 클래스로 옮겼다. 컨트롤러는 얇게 두고, 계산은 전부 서비스에 둔다는 평소 원칙 그대로다.

VBA가 하던 일

CI4 서비스

증빙유형 기반 부가세 자동계산

VatCalculatorService

장부 파일 일괄 입력

LedgerImportService

월·분기 집계 (영업현황표)

SummaryService

정률·정액 감가상각

DepreciationService

소득금액계산서·필요경비명세서·감가상각조정명세서

TaxFormService, TaxFormExporter

하나 더 분리한 것이 있다. 세율, 한도 같은 세법 파라미터다. 엑셀에서는 수식 안에 숫자로 박혀 있던 값들을 tax_parameters 테이블과 TaxRuleResolver로 빼냈고, 운영자 전용 화면에서 고칠 수 있게 했다. 세법이 바뀌어도 배포 없이 값만 바꾸면 된다. "매년 파일 재배포"라는 원본의 한계를 정면으로 푼 부분이다.

파일 하나에서 다중 사용자로

엑셀은 파일 하나가 사용자 하나였다. 웹에서는 격리 단위를 명시해야 한다. AICassa는 user → business(사업장) → ledger 순서로 잡았고, 모든 조회와 집계 쿼리에 user_id와 business_id 스코프를 강제한다. 여기에 귀속연도를 파라미터로 붙여 장부와 서식을 연도별로 나눈다.

사업자등록번호와 연락처를 다루는 서비스라 보안 규칙도 처음부터 넣었다. 입력은 $this->validate(), 출력은 esc(), 쿼리는 Query Builder, POST 폼은 csrf_field(). 프로젝트 CLAUDE.md에 적어둔 규칙이라 클로드코드가 매번 지킨다.

구현 순서는 설명서 목차를 따라가되, 의존 관계에 맞춰 다시 배열했다.

  1. 기반: CI4 세팅, 인증, 멀티테넌시 골격, 계정과목·상각률 시드
  2. 기본사항: 내 정보·사업장, 거래처(등록·수정·CSV 업로드)
  3. 장부 (MVP 핵심): 개별 입력·수정·삭제·복사입력, 부가세 자동계산, 기간·계정 필터 조회
  4. 일괄 업로드: CSV·엑셀 업로드와 검증, 처리결과 표시
  5. 리포트: 영업현황표(월·분기 집계)
  6. 자산·감가상각: 자산대장, 정률·정액 상각
  7. 신고서식: 종합소득세 신고용 3종 서식 생성·출력

"복사입력"은 설명서에서 건진 작은 기능이다. 임차료나 이자처럼 매달 반복되는 거래를 복사해 금액만 고치는 기능인데, 사장님 입장에서는 이게 입력 시간의 대부분을 줄여준다. 요구사항 문서를 따로 쓰지 않아도 기존 매뉴얼이 이런 디테일을 다 알려줬다.

엑셀이 못 하던 것: AI

원본 기능을 옮기고 나니, 웹이라서 가능한 것들이 보였다. 영세사업자가 장부에서 가장 힘들어하는 건 계산이 아니라 입력과 판단이다. 영수증을 보고 금액을 옮겨 적는 일, 이 지출이 소모품비인지 복리후생비인지 고르는 일. 그 지점에 AI를 붙였다.

기능

서비스

하는 일

영수증·세금계산서 인식

ReceiptOcrService

사진에서 일자·거래처·금액·증빙유형 추출

계정과목 추천

AccountClassifierService

거래내용을 보고 계정과목 제안

엑셀 열 자동 매핑

ExcelColumnMapperService

사용자가 쓰던 엑셀의 열을 장부 항목에 연결

자연어 장부 검색

LedgerQueryParserService

"지난 분기 차량유지비" 같은 질문을 검색 조건으로 변환

이상 거래 탐지

AnomalyDetectionService

중복·누락이 의심되는 거래 표시

세무 Q&A

TaxQaService

질문에 답하고 근거 출처를 함께 표시

영수증 인식은 Claude Vision으로 만들었다. 표준화된 세금계산서는 Sonnet으로 충분하고, 흐릿하거나 구겨진 이미지만 Opus로 넘긴다. 결과는 자유 텍스트가 아니라 구조화된 JSON으로 받고, 바로 장부에 넣지 않고 사용자가 확인하는 단계를 거친다. 세무 데이터에서 AI는 초안을 쓰는 쪽이고, 확정은 사람이 한다.

엑셀 열 자동 매핑은 이 프로젝트의 출발점과 이어진다. 이미 엑셀로 장부를 쓰던 사람이 가장 먼저 하고 싶은 건 "내 파일 그대로 올리기"다. 열 이름이 제각각이어도 AI가 매핑을 제안하고 사용자가 확인하면, 이사 비용이 거의 사라진다.

AI 호출에는 비용이 따르니 안전장치도 넣었다. AiBudgetFilter와 AiRateLimitFilter로 사용자별 호출량과 예산을 제한하고, ai_usage_counters 테이블에 사용량을 쌓는다. 월 3~5만원 정액제를 생각하면, 한 사용자가 원가를 넘기지 않게 막는 장치는 기능만큼 중요하다.

돌아보며

이 작업에서 배운 것은 세 가지다.

  1. 요구사항은 이미 어딘가에 있다. 소상공인 도구를 만든다고 하면 인터뷰부터 떠올리기 쉽다. 하지만 이미 많은 사람이 쓰는 도구가 있다면, 그 매뉴얼과 엑셀 시트가 가장 정확한 요구사항이다. 할 일은 상상이 아니라 정독이다.
  2. 옮기는 것과 고치는 것을 구분한다. 계산 로직은 한 글자도 바꾸지 않고 옮기고, 검증으로 그걸 증명했다. 대신 설치·배포·다중 사용자·연도 관리처럼 엑셀이라서 생긴 불편은 과감히 고쳤다. 무엇을 지키고 무엇을 바꿀지 먼저 나눠두니 결정이 쉬웠다.
  3. AI는 사람의 수고가 가장 큰 곳에 둔다. 계산은 이미 엑셀이 잘한다. AI는 영수증 옮겨 적기, 계정과목 고르기, 내 엑셀 올리기처럼 사장님이 실제로 막히는 지점에 넣었고, 확정은 언제나 사람이 하게 했다.

"docs의 문서, 프로그램, 엑셀을 분석해줘"라는 한 줄은 결국 분석 문서 하나, 계산식 명세 하나, 검증 문서 하나, 그리고 CI4 서비스 스무 개 남짓으로 이어졌다. 클로드코드가 VBA를 읽고 표로 정리하는 동안 나는 무엇을 지키고 무엇을 바꿀지 판단하는 데 집중할 수 있었다. 26년 동안 PHP로 일하면서 이렇게 "판단"에만 시간을 쓴 프로젝트는 드물었다.