영수증 정산 — 촬영 한 번이 회계 전표가 되기까지
사진 한 장 찍는 걸로 끝나야 할 일이, 왜 우리 회사에선 항상 “나중에 몰아서”가 됐을까요. 그 질문에서 시작해 모바일 촬영부터 회계 전표 확정까지 한 흐름으로 잇는 기능을 만들었습니다. 무엇을 풀려고 했는지, 어떻게 설계했는지, 만들면서 무엇에 걸려 넘어졌는지를 정리합니다.
1. 왜 만들었나 — 영수증 한 장의 무게
섹션 제목: “1. 왜 만들었나 — 영수증 한 장의 무게”작은 제조사의 회계 처리는 대부분 이렇게 흘러갑니다. 출장을 다녀오거나 거래처와 식사를 하면 영수증이 생기고, 그 영수증은 지갑이나 서랍 어딘가에 며칠, 길게는 한 달을 머뭅니다. 월말이 되면 뒤늦게 꺼내 경리에게 전달하고, 경리는 그 종이를 보며 어떤 계정과목인지, 부가세는 얼마인지, 어느 거래처인지를 하나하나 판단해 전표를 입력합니다. 원본 영수증은 규정상 보관해야 하는데, 그 사이 구겨지거나 잉크가 흐려지는 경우도 드물지 않습니다.
이 흐름의 진짜 문제는 느린 것 자체가 아니라, 판단이 한 사람의 경험에만 의존한다는 점이었습니다. “이 지출은 복리후생비인가 회의비인가”는 매번 사람이 봐야 알 수 있는 문제로 남아 있었고, 그 사람이 자리를 비우면 처리도 함께 멈췄습니다. SL.AIMS를 만들며 계속 붙잡고 있는 질문 — 회사의 두뇌를 사람 머릿속이 아니라 시스템 안에 두려면 어떻게 해야 하는가 — 를 가장 작고 구체적인 단위로 시험해볼 수 있는 기능이 영수증 처리였습니다.
2. 무엇을 만들었나 — 촬영에서 확정까지 한 흐름
섹션 제목: “2. 무엇을 만들었나 — 촬영에서 확정까지 한 흐름”목표는 단순했습니다. 모바일에서 사진을 찍는 순간 정산 프로세스가 시작되게 만들고, 반복되는 판단은 시스템이 먼저 초안을 만들어 사람은 검수만 하게 한다. 사내 업무 모바일 앱 SL Works에 “영수증 보내기” 메뉴를 만들고, 이렇게 흐르도록 했습니다.
- 메뉴에 들어가면 바로 카메라가 뜬다 — 촬영이 첫 화면이지, 몇 단계 눌러서 찾아가는 기능이 아니다.
- 결제 수단(법인카드·개인카드·현금)과 비용 목적을 고르고 메모를 남긴 뒤 제출한다.
- 서버가
DRAFT상태로 접수하고, 회계관리자에게 즉시 알림이 간다. - 포털의 회계관리 > 영수증 처리 화면에 미처리 건수 배지가 뜨고, 담당자가 개별 또는 일괄로 처리한다.
- 시스템이 먼저 계정과목을 매핑해 분개 초안을 만들어 보여주고, 담당자는 계정·적요만 다듬어 확정한다.
3. 데스크탑에서는 이렇게 처리된다 — 자동 분개 초안
섹션 제목: “3. 데스크탑에서는 이렇게 처리된다 — 자동 분개 초안”모바일에서 보낸 영수증은 포털 회계관리자 화면에 그대로 도착합니다. 원본 사진은 영구 보관 대상으로 별도 표시되고, 그 아래 회계 분개 표에는 이미 초안이 채워져 있습니다 — 사람이 계정과목을 처음부터 고르는 게 아니라, 맞는지 확인하고 필요하면 고치는 일만 남습니다.
실제로 처리된 한 건을 예로 들면(가맹점명은 가공), 제출 화면과 자동 생성된 분개는 이렇게 나타납니다.
| 항목 | 값 |
|---|---|
| 제출자 | 신진형 (E0001) |
| 결제 수단 | 개인카드 |
| 비용 목적 | 복리후생비 |
| 금액 | 38,000원 |
| 부가세 | 3,453원 |
| 메모 | 출장식대 |
| 구분 | 계정과목 | 금액 | 적요 |
|---|---|---|---|
| 차변 | 5110 복리후생비 | 34,547 | 복리후생비 - 든든식당 / 출장식대 |
| 차변 | 1350 부가세대급금 | 3,453 | 부가세대급금 - 든든식당 |
| 대변 | 2010 미지급금 | 38,000 | 미지급금 - 든든식당 |
차변 38,000원 / 대변 38,000원으로 균형이 맞고, 담당자는 계정·적요만 확인한 뒤 확정 버튼을 누르면 됩니다.
비용 목적은 아홉 가지로 나누고, 각각을 실제 사용 중인 계정과목에 매핑했습니다.
| 비용 목적 | 차변 계정 |
|---|---|
| 여비교통비 | 5140 |
| 차량유지비 | 5120 |
| 접대비 | 5150 |
| 회의비 | 5160 |
| 복리후생비 | 5110 |
| 소모품비 | 5130 |
| 통신비 | 5170 |
| 교육훈련비 | 5180 |
| 지급수수료 | 5190 |
대변은 결제 수단과 무관하게 **미지급금(2010)**으로 통일하고, 부가세는 **부가세대급금(1350)**으로 따로 분리했습니다. 매핑표에 없는 목적(예: 기타)은 초안의 차변을 비워둔 채로 넘겨 사람이 직접 채우게 했습니다 — 애매한 판단까지 시스템이 억지로 추측하게 만들지 않는 편이 낫다고 봤습니다.
4. 어떻게 만들었나 — 새로 만들지 않고, 있는 것을 재사용했다
섹션 제목: “4. 어떻게 만들었나 — 새로 만들지 않고, 있는 것을 재사용했다”이 기능에서 가장 신경 쓴 설계 원칙은 “확정” 버튼을 눌렀을 때 별도의 회계 로직을 새로 만들지 않는 것이었습니다. 영수증 확정은 결국 전표 하나를 만드는 일이고, SL.AIMS에는 이미 회계 전표 생성 로직(AccountingService.createEntry)이 있었습니다. 영수증 정산 확정 버튼은 이 로직을 그대로 호출합니다.
권한 — 스키마를 건드리지 않고 새 역할을 만들다
섹션 제목: “권한 — 스키마를 건드리지 않고 새 역할을 만들다”영수증 처리는 기존 회계 담당자뿐 아니라 전용 경리 역할이 다룰 수 있어야 했습니다. 그런데 SL.AIMS의 권한 체계는 정적 enum 역할과, 나중에 늘어난 동적 역할 테이블을 함께 읽는(dual-read) 구조로 되어 있습니다. 이 구조 덕분에 스키마 변경이나 마이그레이션 없이 “ACCOUNTANT(경리)“라는 새 역할을 데이터로만 추가하고, 알림 대상과 화면 접근 권한에 반영할 수 있었습니다. 새 권한 하나 추가하려고 DB 구조를 바꾸는 일을 피한 것도 작지만 의도적인 선택이었습니다.
5. 개발 과정에서 부딪힌 벽 — 사진이 안 보이는 미스터리
섹션 제목: “5. 개발 과정에서 부딪힌 벽 — 사진이 안 보이는 미스터리”기능을 다 붙이고 실제로 써보니, 포털에서 영수증 원본 이미지가 뜨지 않는 문제가 있었습니다. 업로드는 분명 성공했는데 화면에는 깨진 이미지 아이콘만 나왔습니다. 원인은 이미지 저장소(MinIO) 접근 방식에 있었습니다.
이미지 URL을 만들 때 흔히 쓰는 방법은 **서명된 임시 URL(presigned URL)**을 발급해 브라우저가 저장소에 직접 접근하게 하는 것입니다. 그런데 서버 환경 설정상 저장소 주소가 localhost로 잡혀 있었습니다. 서버 내부에서는 그 주소가 유효하지만, 외부 브라우저 입장에서 localhost는 자기 자신의 컴퓨터를 가리킬 뿐입니다. 그래서 이미지 요청이 브라우저 자신에게 날아가 연결이 거부됐습니다.
단순히 URL의 호스트 이름만 바꿔치기하면 되지 않을까 싶었지만, 그렇게 간단하지 않았습니다. presigned URL은 서명 자체에 호스트 이름이 포함되어 있어서, 문자열을 치환하는 순간 서명이 깨지고 접근이 거부됩니다. 결국 택한 방법은 presigned URL을 아예 쓰지 않는 것이었습니다. 서버가 이미지를 직접 저장소에서 읽어 스트리밍으로 브라우저에 전달하는 방식으로 바꾸자, 인증을 통과한 요청만 이미지를 받을 수 있게 되면서 문제가 해결됐습니다. 보안 경로 하나가 접근성 문제도 같이 풀어준 셈입니다.
6. 지금 어디까지 왔나 — 정직한 현황
섹션 제목: “6. 지금 어디까지 왔나 — 정직한 현황”2026년 7월 9일 기준으로 촬영→서버 전송→알림→자동 분개→확정까지 전체 흐름이 개발 서버에서 라이브로 동작합니다. 이후에도 전체 선택 후 일괄 안내, 이미지 인라인 표시, 반려 처리, 금액 수정 같은 실사용 편의 기능을 계속 붙이고 있습니다.
다만 아직 비어 있는 부분도 분명합니다. 전용 경리 역할은 만들어졌지만, 그 역할을 실제 담당자에게 배정하는 일은 아직 제 몫으로 남아 있습니다. 지금은 인사 담당 계열 권한으로 임시 접근하고 있는 상태입니다. 기능은 완성됐지만, 조직에 그 기능을 온전히 넘기는 마지막 단계는 시스템이 대신해줄 수 없는 일이라는 걸 다시 확인한 셈입니다.