AX 프로젝트 · 추진 사례

AX 프로젝트 추진 사례
업무관리시스템을 10회차로 완성하기

구글 시트와 에어테이블에 흩어져 있던 일을 하나의 시스템으로 합치는 과정을 —
과제 정의부터 배포·오픈베타·효과 정량화까지, 실제로 만들어진 순서 그대로 따라갑니다.
사례사내 업무관리시스템 · 완성 후 운영 중
구성10회차 컨설팅 프로세스에 1:1 대응
근거실제 결정 로그 D1~D46 · 화면 캡처
규모8개 테이블 · 화면 10종 · 데이터 1,400여 건

전체 그림 — 무엇을 만들었고, 왜 이 순서였나

이 회차에서 다루는 것
  • 우리가 어떤 업무를 자동화했고, 결국 어떤 시스템으로 끝났는가.
  • 10회차 프로세스의 각 회차가 우리에게 무엇으로 나타났는가.
  • 이 문서를 읽는 방법 — 결정 로그·화면 캡처·측정값을 어떻게 따라갈 것인가.

출발점 — 업무가 여러 도구에 나뉘어 있었다

우리 회사는 교육·컨설팅이 주업무입니다. 고객 문의가 오면 영업 → 견적 → 수주 → 일정 배정 → 강의 수행 → 계산서 발행 → 수금 → (외부강사) 정산까지 한 줄기로 흘러갑니다. 그런데 그 한 줄기가 여러 개의 도구에 나뉘어 있었습니다.

도구담당하던 것문제
구글 시트 회사 캘린더누가 언제 어디서 강의하는지 — 월별 블록 그리드일정만 있고 돈이 없다. 셀에 적힌 정보는 사람만 읽을 수 있다
에어테이블 프로젝트 목록계약·금액·단계·계산서돈만 있고 일정이 없다. 날짜가 비어 있어도 강의를 안 한 것이 아니다
엑셀 외부인건비 지급대장외부강사 원천징수·실지급앞의 두 곳과 아무 연결이 없다. 수식은 회계법인이 준 그대로

영업이 성사되면 시트와 에어테이블 양쪽에 이중 등록해야 했고, 외부 강사에게 줄 돈은 세 번째 파일에서 따로 계산했습니다. 그리고 어느 쪽도 "이 프로젝트에서 우리가 얼마를 청구했고 얼마가 아직 안 들어왔는지"를 한 화면에서 답하지 못했습니다. 10회차의 목표는 이 셋을 하나로 합치는 것이었습니다.

8
테이블
(고객사·프로젝트·계산서·강의·멤버·일정·지급·견적)
10
화면
(캘린더·프로젝트·강의·계산서·견적서·고객사·멤버·지급·대시보드·관리자)
1,400+
이관된 데이터
(기존 시트·에어테이블·엑셀에서)
46
결정 로그
(D1~D46, 무엇을 왜 그렇게 정했나)
완성된 시스템의 캘린더 화면 — 주 단위 연속 그리드에 사람별 행이 놓이고 강의가 음영 칩으로 표시된다
10회차를 통과한 뒤의 캘린더. 구글 시트의 월별 블록을 주 단위 연속 그리드로 바꾸고, 행은 내부 멤버 고정 순서 + 회사 + 외부 강사입니다. 칩 하나가 강의 1건 = 하루 × 강사 1명이고, 이 칩이 프로젝트·계산서·정산까지 연결됩니다. (이 문서의 모든 캡처는 고객사명·인명·금액을 익명화한 사본에서 찍었습니다)

10회차 대응표 — 우리는 각 회차에서 무엇을 했나

10회차 프로세스가 회차마다 던지는 핵심 질문에 우리가 어떻게 답했는지를 먼저 펼쳐 둡니다. 이 문서의 각 회차는 이 표의 한 줄을 자세히 푼 것입니다.

회차핵심 질문우리의 답
1회차무엇을 자동화하는가?흩어진 3개 도구를 하나로. 프로젝트 : 계산서 : 강의 = 1 : N : N 3층 구조를 정하고 개발계획서를 씀
2회차자동화가 정말 가능한가?캘린더 한 화면을 심장으로 잡고 구현. 목업 대신 실데이터를 먼저 이관해 검증(개발 순서를 바꿈)
3회차아웃풋 품질은 몇 점인가?강의–프로젝트 연결률 24% → 89%. 원천징수 계산을 지급대장 36행과 100% 대조
4회차기획한 기능이 전부 동작하는가?7개 테이블을 공용 DataTable로. 엑셀처럼 쓰는 인라인 편집까지 붙임
5회차어디까지 확장할 것인가?내부·외부 강사 테이블 물리 통합(재배정을 한 필드로), 지급대장 개편, 견적서·메일 발송 연계
6회차누구나 접속할 수 있는가?로그인·가입 승인제. 배포 어댑터가 막혀 게이팅 방식을 통째로 갈아엎고 서버 액션 46개에 인증 삽입
7회차낯선 사용자에게 맡겨도 되는가?동시 요청 부하 시험 에러 0, 보안 헤더, 문서 4종 정비
8회차실제로 쓰이는가?DB를 운영용으로 전환하고 파일 저장소를 옮김. 로컬↔운영 동기화 도구를 만들어 실사용 시작
9회차데이터가 시키는 개선은?쓰면서 드러난 용어 혼동을 DB 단위로 리네임, 날짜 시간대 버그 수정, 화면 필터 재설계
10회차무엇이 얼마나 좋아졌는가?배포처를 바꿔 완주. 결정 로그 46건이 그대로 인수인계 문서가 됨

이 문서를 읽는 방법

진행 방식회차별로 ①무엇을 하는 회차인지 → ②우리가 실제로 한 것 → ③그때 내린 결정 → ④결과 화면·숫자 → ⑤산출물 순으로 읽습니다. 완료 기준: 각 회차 끝의 산출물 목록을 보고 "우리 과제라면 이 자리에 무엇이 놓일지" 말할 수 있는가.

1결정 로그를 눈여겨보십시오

파란 D번호 블록은 우리 개발 노트에서 그대로 가져온 것입니다. 잘한 결정만이 아니라 틀려서 되돌린 결정도 그대로 실려 있습니다 — 그게 이 기록의 값어치입니다.

2숫자를 눈여겨보십시오

"잘 됐다"가 아니라 연결률 89%, 36행 100% 일치, 동시 80요청 에러 0처럼 측정값으로 적혀 있습니다. 3회차·10회차가 요구하는 것이 바로 이 형태입니다.

3화면은 최종본입니다

캡처는 완성된 시스템에서 찍었습니다. 각 회차 본문은 "이 화면이 그때 어디까지였는지"를 글로 적어 두었으니, 화면과 설명의 시점 차이를 감안하고 보십시오.

데이터는 전부 가명입니다. 캡처에 보이는 고객사명·담당자·강사·계좌·이메일은 실제 값이 아니라 익명화한 사본이고, 금액도 일정 배율로 바꿨습니다. 다만 건수·비율·합계 관계는 실제 그대로라 화면의 산술은 맞아떨어집니다.

1회차 — 과제 정의와 개발계획서

이 회차에서 다루는 것
  • 흩어진 실물 자료에서 SIPOC과 등장인물(테이블)을 뽑아내는 과정을 따라갈 수 있다.
  • 사람이 하던 프로세스를 그대로 옮기지 않고 재설계한 지점을 짚을 수 있다.
  • 3층 데이터 구조(프로젝트:계산서:강의)가 왜 필요했는가.

기존 업무 방식 — 실제로 쓰던 파일 세 개

과제 정의는 "실물을 보는 것"에서 시작합니다. 우리가 쓰던 파일 세 개를 그대로 펼쳐 놓고 시작했습니다. 아래 캡처는 실제 원본 파일이고, 고객사명·인명·계좌·금액만 가명으로 바꾼 것입니다.

① 구글 시트를 엑셀로 내려받은 회사 캘린더

회사 캘린더 엑셀 — 월별 블록 그리드에 사람별 행이 있고, 각 셀에 고객사 약칭이 적혀 있으며 일부 셀 우측 상단에 붉은 노트 표시가 있다
월별로 블록이 끊기는 그리드입니다. 세로는 사람, 가로는 날짜이고, 칸 안의 글자는 고객사 약칭입니다. 오른쪽 위 붉은 삼각형이 셀 노트 — 시간·장소·주제·담당자 연락처가 여기 숨어 있습니다. 아래 노란 상자가 그 노트의 실제 내용입니다.

이 한 장에서 1회차의 문제가 전부 보입니다. 일정은 있는데 금액이 없습니다. "이 칸의 약칭이 어느 회사인가"는 사람 머릿속에만 있습니다. 정작 쓸모 있는 정보(시간·장소·연락처)는 셀 값이 아니라 노트에 숨어 있어서, 값만 읽는 프로그램에는 존재하지 않는 것과 같습니다. 달이 바뀌면 블록이 끊겨 "지난주와 이번 주"를 나란히 볼 수 없습니다.

② 에어테이블에서 내보낸 프로젝트 목록

에어테이블 내보내기 CSV — 고객사·프로젝트명·매출분류·업무 단계·계산서·수금·총계약금액 열이 있고 수행날짜 열은 상당수 비어 있다
계약·금액·단계·계산서·수금이 다 있습니다. 대신 수행날짜 열이 비어 있는 행이 많습니다 — 일정은 시트에서 관리했으니까요. 그래서 "날짜가 없다 = 강의를 안 했다"가 아닙니다. 이 사실을 모르면 이관 규칙을 통째로 잘못 세웁니다.

③ 외부 인건비 지급대장 엑셀

지급대장 엑셀 — 대상자·내역·구분·share 열과 소득세·지방세·원천징수 합계·실지급액이 IF와 ROUNDDOWN 수식으로 계산되는 모습
노란 칸이 수식입니다. 소득세·지방세·원천징수 합계·실지급액이 IFROUNDDOWN으로 계산됩니다. 회계법인이 준 이 수식이 업무 규칙의 원본이고, 3회차에서 이걸 코드로 옮긴 뒤 전 행과 대조하게 됩니다.
세 파일 사이에는 아무 연결이 없습니다. 같은 고객사가 시트에서는 약칭, 에어테이블에서는 정식명, 지급대장에서는 아예 등장하지 않습니다. 과제의 정체는 "새 화면 만들기"가 아니라 흩어진 세 개를 잇는 규칙을 찾아내는 일이었습니다.
듣는 분들이 먼저 해 볼 것. 지금 쓰는 파일을 있는 그대로 열어 놓고 위 네 가지를 물어보십시오 — 무엇이 없는가 / 사람 머릿속에만 있는 규칙은 무엇인가 / 값이 아니라 주석·서식·색에 담긴 정보는 없는가 / 파일끼리 무엇으로 이어지는가. 이 네 답이 1회차 SIPOC의 재료입니다.

STEP 1 · 업무 범위와 SIPOC

우리 SIPOC은 다음과 같이 정리됐습니다. 중요한 것은 I(인풋)에 "실물 파일 이름"이 적혀 있다는 점입니다. "일정 정보"가 아니라 회사캘린더.xlsx의 일정표(2026) 시트라고 적어야 그다음 단계가 굴러갑니다.

구분내용
S 공급자영업 담당(계약·금액), 강의를 수행하는 내부 멤버·외부 강사(일정·실적), 회계법인(원천징수 기준), 고객사(발주·수금)
I 인풋에어테이블 내보내기 프로젝트 목록 CSV(계약·금액·단계·계산서), 구글시트 회사캘린더 xlsx(월별 블록 그리드), 외부인건비 지급대장 xlsx(지급내역·계좌 시트)
P 프로세스① 견적 작성·발송 → ② 프로젝트 등록 → ③ 캘린더 일정 배정(강의↔프로젝트 연결) → ④ 강의 수행 → ⑤ 계산서 발행 → ⑥ 수금 추적 → ⑦ 외부강사 지급행 생성 → ⑧ 지급
O 아웃풋캘린더·프로젝트·계산서·지급 화면, 견적서 PDF와 발송 이력, 대시보드(계약총액·발행완료·미청구·미수금)
C 고객전 직원(조회·입력), 관리자(개인별 실적), 외부 강사(정산 수령), 고객사(견적서 수신)
범위 선긋기. 우리가 "만들지 않을 것"으로 먼저 잘라낸 것들 — 외부 강사 모집·지원 기능(다른 시스템의 스키마를 참고하되 모집 관련은 전부 제외), 주별 강의 일수 카운트(요구가 없었음), 계산서 자동 생성(뒤에서 다시 다룹니다). 이 목록이 없었으면 10회차 안에 끝나지 않았을 것입니다.

STEP 2 · 휴먼 프로세스를 프로그램 프로세스로

가장 중요한 재설계는 "셀 하나에 사람이 알아서 적던 것"을 프로그램이 다룰 수 있는 형태로 바꾸는 일이었습니다. 구글 시트에서는 한 칸에 고객사·시간·장소·담당자 연락처가 자유 문장으로 섞여 있었습니다.

휴먼 — 구글 시트 셀
  • 날짜 × 사람 행의 위치로 "누가 언제"를 표현
  • 칸 안의 텍스트는 고객사 약칭(사람만 해석 가능)
  • 시간·장소·연락처는 셀 노트에 숨어 있음
  • 같은 회사를 부르는 이름이 여러 개(약칭·경유처 표기)
  • x 표시 = "이 날 잡지 말 것" 신호
프로그램 — 강의 테이블
  • 강의 1건 = 하루 × 강사 1명이라는 최소 단위 확정
  • 고객사는 별도 테이블의 id로 연결(이름은 바뀌므로)
  • 시간·장소·메모를 구조화 필드로 분리
  • 약칭↔정식명 매핑 규칙을 이관 코드에 명시
  • x는 이관 시 "삭제" 분류로 처리

"강의 1건 = 하루 × 강사 1명"이라는 정의가 우리 시스템 전체를 지탱합니다. 이 단위가 있어야 개인별 실적(일수·시간·강사료)이 집계되고, 외부강사 지급행이 자동으로 생기고, 캘린더 칩이 그대로 정산 근거가 됩니다. 단위를 "프로젝트 단위"나 "회차 단위"로 잡았다면 전부 다시 만들어야 했을 것입니다.

함정. "사람이 하던 대로"는 요구사항이 아니라 관성입니다. 반대로, 사람이 순식간에 하던 판단(이 약칭과 저 약칭이 같은 회사라는 것)은 프로그램에겐 명시적 규칙이 필요합니다. 우리도 그 규칙을 미루었다가 3회차에 크게 되돌아왔습니다.

STEP 3 · 데이터 정의 — 세 단위가 다르다는 발견

진행 방식실물 자료에서 등장인물을 뽑아 테이블로 만듭니다. 완료 기준: 테이블마다 필드·연결·관계 종류가 적혀 있고, "이 구조로 답해야 할 질문"이 함께 적혀 있는가.

우리 설계의 핵심 판단은 프로젝트·계산서·강의가 서로 다른 단위라는 것을 인정한 데 있습니다. 한 프로젝트에 계산서가 2장 나가고 강의가 5건 있는 경우가 실제로 있었습니다. 이걸 한 테이블에 욱여넣으면 어느 쪽 기준으로도 집계가 안 됩니다.

확정된 3층 구조
고객사 (clients)
 └─ 프로젝트 (projects)      사업·계약 단위. 계약 총액(부가세 별도) 보유
     ├─ 계산서 (invoices)    전자세금계산서 발행 단위       [N:1]
     └─ 강의 (lectures)      하루 × 강사 1건                [N:1]

멤버 (members) ─→ 강의        내부 멤버 · 외부 강사
멤버 ─→ 지급 (payments)       외부 인건비 지급대장
일정 (events)                 미팅 · 휴가 · 기타 · 공휴일 (강의 외)

계산서와 강의를 직접 연결하지 않는다

둘 다 프로젝트에만 매답니다. 계산서 1장이 강의 3건에 대응하는지 5건에 대응하는지는 업무상 정해져 있지 않고, 억지로 연결하면 헷갈리기만 합니다. 대신 집계 규칙을 이렇게 정의했습니다.

지표정의쓰이는 곳
회사 매출발행완료 계산서의 공급가액 합대시보드 · 월/연 매출
개인 실적강의 강사료의 합관리자 화면 · 개인별 일수·시간·강사료
미청구프로젝트 계약총액 − 발행완료 계산서 합대시보드 · "발행 누락" 추적
이 구조로 답해야 할 질문을 먼저 적었습니다. "이번 달 매출은?", "이 사람 이번 분기 강의 일수는?", "수행은 끝났는데 계산서를 안 끊은 프로젝트는?" — 설계가 끝난 게 아니라, 이 질문들에 SQL 한 방으로 답할 수 있으면 설계가 끝난 것입니다.

STEP 4~5 · 화면 기획과 개발계획서

백엔드는 위의 8개 테이블, 프론트엔드는 화면 목록으로 정리했습니다. 이 목록이 그대로 10회차 뒤의 좌측 네비게이션이 됩니다.

화면보여주는 것할 수 있는 것
캘린더주 단위 연속 그리드 · 사람별 행 · 강의 음영일정 추가, 드래그로 날짜·담당자 이동
프로젝트계약·금액·단계·수행자·계산서 수검색·필터·그룹(서브토탈), 상세에서 강의·계산서 편집
강의고객사·프로젝트·강사·날짜·시간·장소·강사료엑셀식 인라인 편집
계산서발행상태·수금상태·공급가액·세액발행 요청·상태 변경
지급외부강사 share·원천징수·실지급소득구분 선택 시 자동 계산
대시보드 / 관리자매출·미청구·미수금 / 개인별 실적기간 조회 (관리자 화면은 권한 분리)
D10
기술 스택은 AI에게 묻고, 최신 버전을 그대로 받았다

계획 문서에는 한 버전 아래로 적어 두었지만, 스캐폴드 도구가 설치한 최신 조합을 그대로 썼습니다. 기능이 동일하고 문제가 없었기 때문입니다. 계획서와 실제가 다르면 실제를 기록에 반영하는 것이 결정 로그의 역할입니다.

1회차 산출물
  • 개발계획서 — SIPOC · 3층 데이터 모델 · 테이블 정의 · 화면 목록 · 로드맵 · 미결 항목
  • 이관 규칙 초안 — 약칭↔정식명, 삭제/미팅/기타 분류, 강사료 배분 원칙
1회차를 끝낼 때 확인한 것
  • 인풋이 "실물 파일 이름 + 시트명"까지 적혀 있다.
  • 자동화의 최소 단위(무엇 1건 = 무엇)가 한 문장으로 정의돼 있다.
  • 테이블 간 관계(1:N)와 "연결하지 않기로 한 것"이 명시돼 있다.
  • 이 구조로 답해야 할 질문 목록이 있다.
  • 만들지 않을 것 목록이 있다.

2회차 — MVP: 캘린더 한 장을 관통시킨다

이 회차에서 다루는 것
  • 전체 기획 중 무엇을 MVP의 심장으로 골랐고, 기준은 무엇이었는가.
  • 목업 대신 실데이터를 먼저 넣기로 한 판단과 그 이유.
  • MVP가 답해야 했던 "자동화 가능성 질문"은 무엇이었는가.

핵심 흐름 선정 — 왜 캘린더였나

화면 10개 중 캘린더를 먼저 만든 이유는 세 가지 기준이 전부 거기서 겹쳤기 때문입니다.

기준질문우리의 답
가치의 중심약속한 효과가 실제로 발생하는 기능은?구글 시트를 대체하는 순간 이중 등록이 사라진다
흐름의 척추인풋이 아웃풋이 되기까지 반드시 거치는 최단 경로는?시트 셀 → 강의 1건 → 프로젝트 연결 → 집계
불확실성"정말 될까?" 가장 자신 없는 구간은?셀 위치(날짜×사람행)만으로 강의를 정확히 복원할 수 있는가
세 번째가 결정적입니다. 캘린더 화면을 예쁘게 그리는 것은 어렵지 않습니다. 진짜 질문은 "기존 시트 데이터를 손실 없이 옮길 수 있는가"였고, 그게 안 되면 나머지 9개 화면은 만들 이유가 없었습니다. MVP는 보여 주기 좋은 것이 아니라 가장 의심스러운 구간을 고릅니다.

개발 순서를 바꾼 결정 — 목업보다 실데이터 먼저

진행 방식MVP 범위를 확정하고 구현합니다. 완료 기준: 인풋 → 아웃풋 한 사이클이 실제 데이터로 관통되는가.

D9
스키마·데이터 이관을 공용 테이블 컴포넌트보다 앞으로 당겼다

원래 계획은 화면용 테이블 컴포넌트를 먼저 만들고 목업 데이터로 확인하는 순서였습니다. 그런데 목업으로 테이블을 만들 바에는 실데이터를 먼저 확보해서 모든 화면을 처음부터 실데이터로 개발·검증하는 편이 낫다고 판단해 순서를 바꿨습니다.

이유: 목업과 실데이터가 어긋나면 화면을 두 번 만들게 됩니다. 그리고 우리 과제에서는 데이터 정확성이 곧 성패였으므로 이관 스크립트를 먼저 검증해야 했습니다.

그래서 2회차의 실제 작업은 "캘린더 화면 만들기"가 아니라 "이관 스크립트 + 캘린더 화면"이 되었습니다. 첫 이관 결과는 다음과 같았습니다.

92
고객사
160
프로젝트
756
강의(셀)
185
그중 프로젝트에 연결된 강의

강의 756건 중 185건(24%)만 프로젝트에 붙었습니다. 나머지 571건은 "(미연결 강의)"라는 임시 프로젝트에 격리했습니다. 중요한 것은 버리지 않았다는 점입니다 — 전량을 DB에 적재하되 연결만 보류했습니다.

D11
이관은 "초안 뼈대" 수준으로 끊고, 정확도는 다음 회차로 넘겼다

고객사 매칭이 부분일치만 지원해서 약칭·경유처 표기·회차별 강사 배분을 아직 반영하지 못한 상태였습니다. 여기서 완벽을 추구하는 대신 "데이터 유실 0, 연결 정확도는 측정 대상"으로 정의하고 3회차로 넘겼습니다.

이 판단이 3회차를 만들었습니다. 24%를 실패로 보고 2회차에서 붙잡고 있었다면 개선 사이클이 생기지 않았을 것입니다. "지금 몇 점인지 아는 상태"로 회차를 끝내는 것이 다음 회차의 재료가 됩니다.
2회차 산출물
  • MVP 범위표 — 만들 것(캘린더·이관) / 미룰 것(나머지 화면·인증·정산)
  • MVP 코드 + OVERVIEW.md — 실행 방법, 현재 동작 범위
  • 자동화 가능성 실험 기록 — 연결 185/756 = 24%, 미연결 571건 격리
  • 결정 로그 시작 — D1~D13

3회차 — 하네스 엔지니어링: 품질을 숫자로 만든다

이 회차에서 다루는 것
  • 우리가 품질 지표를 무엇으로 정의했는가.
  • 현수준 측정 → 원인분석 → 개선 → 재측정 사이클의 실제 사례를 따라갈 수 있다.
  • 계산 로직을 원본과 전수 대조해 검증한 방법.

품질 지표 정의 — 무엇을 몇 점으로 잴 것인가

우리 아웃풋은 "화면"이 아니라 "옮겨진 데이터"였습니다. 그래서 지표도 데이터 정확도로 잡았습니다.

지표정의현수준판정 기준
강의–프로젝트 연결률연결된 강의 ÷ 전체 강의24%미연결분은 앱에서 수동 배정 가능해야 함
강사료 배분 정확도회차별 강사료 합 = 견적 공급가액미측정마진 0 — 합이 정확히 일치해야 함
원천징수 계산 일치율계산 결과 = 지급대장 엑셀 값미측정100% 외에는 의미 없음(돈이므로)
숨은 정보 복원율셀 노트에서 살려낸 시간·장소·메모 건수0건많을수록 좋음
지표를 정할 때 "100%가 아니면 의미 없는 것""높을수록 좋은 것"을 구분하십시오. 돈 계산은 전자이고, 데이터 매칭은 후자입니다. 이 구분이 개선의 우선순위를 정합니다.

원인분석 → 개선 → 재측정 ①: 연결률 24% → 89%

진행 방식측정값이 낮은 원인을 찾아 고치고, 같은 방법으로 다시 잽니다. 완료 기준: Before/After가 같은 지표로 비교되는가.

원인은 사람만 알던 규칙이 코드에 없다는 것이었습니다. 구체적으로 세 가지였습니다.

원인사람은 어떻게 알았나코드에 넣은 규칙
고객사 약칭"이 약칭은 저 회사"라고 다 알고 있음별칭 매핑 표를 이관 코드에 명시
경유처 표기교육 플랫폼을 통해 들어온 건은 회사명이 두 개정식명 + 경유처 접미 형태로 통일
회차별 강사 배분같은 프로젝트라도 회차마다 메인·보조가 다름회차별 배분 규칙을 금액까지 코드화
D16
이관 스크립트를 전면 재작성하고, 검증 대상을 정해 대조했다

규칙을 덧붙이는 대신 이관 스크립트를 다시 썼습니다. 그리고 규칙이 가장 복잡한 고객사 한 곳을 검증 표본으로 삼아 회차별 건수·인당 금액·강사료 합계를 전부 대조했습니다 — 전부 일치.

결과: 강의 555건 중 연결 493건(89%), 미연결 62건. 강사료는 429건이 채워졌고 그중 88건은 명시 규칙, 341건은 공급가액 ÷ 강의수 균등 배분입니다.

24% → 89%
강의–프로젝트 연결률
62건
남은 미연결 (앱에서 배정)
429건
강사료 채워진 강의
일치
검증 표본 회차별 건수·금액
남은 62건을 0으로 만들지 않았습니다. 대신 프로젝트 테이블에 "강사료 합계 ≠ 공급가액이면 주황색" 표시를 넣어, 사람이 화면에서 바로 찾아 고칠 수 있게 했습니다. 자동화가 90%에서 멈출 때 나머지 10%를 사람에게 넘기는 통로를 만드는 것도 설계입니다.

원인분석 → 개선 → 재측정 ②: 원천징수 36행 전수 대조

외부 강사에게 지급할 때는 소득세와 지방세를 떼야 합니다. 이 계산은 틀리면 안 되는 종류입니다. 처음 구현은 "원 단위 내림"이었고, 틀렸습니다.

D6
원천징수 계산을 지급대장 엑셀 수식 그대로 이식했다 — 한 번 틀린 뒤에

실제 수식은 10원 단위 절사이고, 게다가 순차 계산이었습니다. ① 소득세 = 내림(share × 세율, 10원 단위) → ② 지방세 = 내림(① × 10%, 10원 단위) → ③ 원천징수 = ① + ② → ④ 실지급 = share − ③. 지방세를 share에서 직접 계산하면 값이 달라집니다.

검증: 지급대장에서 값이 채워진 전 행(36행)과 대조해 100% 일치를 확인했습니다.

소득구분소득세지방세실지급
사업소득3%소득세의 10%share − 원천징수 (합계 3.3%)
기타소득8%소득세의 10%share − 원천징수 (합계 8.8%)
전자세금계산서원천징수 없음share × 1.1 (부가세 가산)
지급 화면 — 회계연월·강의일·소득구분·share·소득세·지방세·원천징수합계·실지급액 컬럼과 하단 합계행
완성된 지급 화면. 소득구분을 고르면 소득세·지방세·원천징수 합계·실지급액이 자동으로 계산됩니다. 하단 합계행은 필터된 전체의 합이라, 지급대장 엑셀과 그대로 대조할 수 있습니다.
돈 계산을 검증하는 방법은 하나뿐입니다 — 기존에 쓰던 원본과 전수 대조. 표본 몇 건이 아니라 값이 있는 행 전부입니다. 이것이 3회차에서 만드는 "하네스"의 전형입니다.

버려질 뻔한 정보를 되찾다 — 셀 노트 1,022개

측정을 하다 보면 "없다고 생각했던 것이 사실 있었다"는 발견이 나옵니다. 우리에게 그 순간은 셀 노트였습니다 — 1회차에서 본 캘린더 캡처의 붉은 삼각형이 그것입니다.

D17
정보가 사라진 게 아니라, 값만 읽던 이관이 버리고 있었다

내려받은 xlsx 안에 셀 노트 1,022개가 주석으로 그대로 보존돼 있었습니다. 노트는 정형 템플릿(시간 / 장소 / 주제 / 담당자 연락처 / 특이사항)이었습니다.

노트 파서를 붙이고 시간 표기를 정규화(예: "1시~3시", "오후3시" → 24시간 HH:MM, 애매하면 원문을 메모에 보존)한 결과, 강의 555건 중 시간 209 · 장소 142 · 메모 181건을 복원했습니다.

원본을 다시 보십시오. "우리 데이터는 부실하다"는 판단은 대개 읽는 방법이 부실했다는 뜻입니다. 이 발견 덕분에 캘린더 칩이 "고객사 – 13~15시 – 장소" 형태로 자동 표시되기 시작했습니다.
3회차 산출물
  • 품질 지표 정의서 — 4개 지표와 판정 기준
  • 측정·개선 리포트(Before/After) — 연결률 24%→89%, 원천징수 36행 100% 일치, 노트 복원 532건
  • 재사용 가능한 검증 스크립트 — 이후 회차에서 데이터가 바뀔 때마다 재실행
3회차를 끝낼 때 확인한 것
  • 지표가 "몇 점"으로 표현되고, 측정 방법이 재현 가능하다.
  • 개선 전후를 같은 지표·같은 방법으로 비교했다.
  • 돈·법 관련 계산은 원본과 전수 대조했다.
  • 자동화가 못 미친 잔여분을 사람이 처리할 통로가 화면에 있다.

4회차 — MVP를 넘어: 기능 목록 100% 구현

이 회차에서 다루는 것
  • MVP 이후 추가 기능을 어떤 순서로 붙였는가.
  • 같은 조작감을 여러 화면에 퍼뜨리기 위해 무엇을 공용화했는가.
  • 기능 확인과 데이터 확인을 어떤 기준으로 했는가.

무엇부터 붙였나 — 화면을 늘리지 않고 "테이블"을 늘렸다

남은 화면은 프로젝트·강의·계산서·고객사·멤버·지급이었습니다. 이들은 겉보기엔 다르지만 전부 "목록 + 검색·필터·정렬·그룹 합계 + 상세 편집"이라는 같은 뼈대였습니다. 그래서 화면을 하나씩 만드는 대신 공용 DataTable을 만들고 컬럼 설정 파일만 화면마다 따로 두었습니다.

D20
스프레드시트 라이브러리를 쓰지 않기로 했다

강의 화면에 엑셀식 편집을 넣기 위해 전용 스프레드시트 라이브러리를 검토했지만 채택하지 않았습니다. 이유: 그 라이브러리는 이미 만들어 둔 공용 툴바(검색·그룹·필터·정렬)와 통합되지 않아 강의 화면만 이질적이 되기 때문입니다.

대신 이미 쓰고 있던 테이블 라이브러리의 컬럼 순서·폭 기능과 드래그 라이브러리를 공용 DataTable에 흡수시켰습니다. 모든 테이블이 같은 조작감을 유지합니다.

이 결정 덕분에 이후 회차에서 화면이 늘어나도 사용자가 새로 배울 것이 없었습니다. 한 테이블에서 익힌 조작이 일곱 테이블에서 그대로 통합니다.

프로젝트 목록 화면 — 고유번호·고객사·프로젝트명·매출분류·단계·수행자·수금유형 컬럼과 하단 합계행
프로젝트 목록. 상단 툴바(검색·그룹·정렬·enum 필터)와 하단 합계행(건수·금액 합)이 전 테이블 공통입니다.
강의 목록 화면 — 고객사·프로젝트·강의명·강사·날짜·시간·장소·강사료 컬럼
강의 목록. 셀을 클릭해 바로 타이핑하고, 방향키·Tab·Enter로 이동하며, 범위 선택과 복사·붙여넣기가 됩니다.
계산서 목록 화면 — 발행상태·수금상태·공급가액·세액·총액 컬럼
계산서. 발행상태와 수금상태가 별개의 축입니다 — 발행했다고 수금된 것이 아니므로.
고객사 목록 화면 — 고객사명·사업자등록증·프로젝트 금액 합계 컬럼
고객사. 행을 열면 연결된 프로젝트 목록이 함께 보여, 관계형 구조가 화면에서 그대로 읽힙니다.

3층 구조가 한 화면에서 만나는 곳 — 프로젝트 상세

프로젝트 상세 드로어 — 프로젝트 정보 아래에 연결된 강의 3건과 계산서 1건이 표로 붙어 있고, 강사료 합과 공급가액이 표시된다
1회차에 정한 프로젝트 : 계산서 : 강의 = 1 : N : N이 화면으로 나타난 모습입니다. 위쪽은 계약 정보, 아래는 이 프로젝트에 매달린 강의 3건계산서 1건. 오른쪽 위 강사료 합 1,950,000원이 공급가액과 같은지가 3회차에서 만든 검산 포인트이고, 여기서 어긋나면 색으로 드러납니다. 강의·계산서를 이 화면에서 고치고 저장 한 번으로 커밋합니다 — 필드마다 서버에 저장하던 방식을 5회차에 이렇게 바꿨습니다.

기능 확인과 데이터 확인 — 두 가지는 다른 일이다

진행 방식기능 목록 대비 구현 여부와, 화면에 뜬 값의 정확성을 따로 점검합니다. 완료 기준: 두 점검이 각각 기록으로 남았는가.

점검질문우리가 실제로 한 것
기능 확인기획한 화면·동작이 전부 있는가화면 목록 대비 전 페이지 응답 확인 + 타입·린트 0건을 회차마다 반복
데이터 확인화면에 뜬 값이 원본과 같은가계약총액 합·계산서 합·강의 건수를 이관 원본과 대조. 합계행을 화면에 상시 노출
D25
합계를 화면에 붙박이로 만들었다

필터를 걸면 필터된 전체의 합이 표 하단에 고정 표시되고, 헤더도 스크롤에 고정됩니다. 검증을 위해 따로 리포트를 뽑을 필요 없이 쓰면서 검산이 됩니다.

인라인 편집은 검증 도구이기도 했습니다. 이관이 89%에서 멈춘 나머지를 사람이 화면에서 바로 고칠 수 있게 되자, 데이터 정확도가 개발과 무관하게 계속 올라갔습니다. 4회차의 진짜 산출물은 기능이 아니라 "사람이 고칠 수 있는 상태"였습니다.
4회차 산출물
  • 기능 확인 결과표 — 화면 목록 대비 구현·응답 점검
  • 데이터 검증 보고 — 합계 대조, 잔여 불일치의 화면 표시 방식
  • 기능 완성 코드 + 갱신된 OVERVIEW.md

5회차 — 고도화와 인접 시스템 연계

이 회차에서 다루는 것
  • 쓰다 보니 드러난 구조적 한계를 어떻게 되돌려 고쳤는가.
  • 위험한 스키마 변경을 안전하게 수행한 절차.
  • 시스템 밖(메일·PDF)으로 확장한 연계 기능.

고도화 ① — 테이블 두 개를 하나로 합친 대공사

1회차 설계에서는 내부 멤버외부 강사를 별도 테이블로 두었습니다. 성격이 다르니 자연스러운 판단이었습니다. 그런데 쓰다 보니 문제가 드러났습니다 — 퇴사자입니다. 내부 직원이 퇴사하면 외부 강사가 되고, 그 사람의 과거 강의는 그대로 남아 있어야 합니다.

합치기 전
  • 강의에 두 개의 외래키(내부용·외부용)
  • 내부↔외부 재배정 = 한 키를 지우고 다른 키를 채움
  • 강사 지정 값에 접두사로 구분을 심어야 함
  • 집계 쿼리가 두 갈래로 갈라짐
합친 뒤
  • 강의에 강사 필드 하나
  • 재배정 = 필드 값 하나 교체
  • 구분은 멤버 테이블의 속성(내부/외부)
  • 집계가 한 갈래
D26
운영 중인 스키마를 3단계로 나눠 안전하게 바꿨다

이미 데이터가 들어 있는 테이블을 합치는 작업이라, 한 번에 하지 않고 나눴습니다. ① 새 구조를 추가만 하고(기존 유지) → ② 데이터를 옮기고 양쪽이 맞는지 검증 → ③ 그 뒤에야 옛 컬럼·테이블 제거. 작업 전 DB 백업.

검증 결과: 내부 7명·외부 20명 통합, 강의 배정 483건·미배정 68건, 지급 111건 전부 연결 유지.

함정도 있었습니다 — 기존 내부 멤버들의 새 컬럼이 빈 문자열이라 조회 시 파싱 오류가 났고, 빈 목록으로 채워 넣어 해결했습니다.

멤버 화면 — 내부 멤버와 외부 강사가 각각의 표로 분리되어 표시되고, 외부에만 정산유형·계좌 컬럼이 있다
합친 뒤의 멤버 화면. DB에서는 한 테이블이지만 화면에서는 내부·외부를 두 개의 표로 나눠 보여 줍니다. 물리 구조와 표현을 분리한 예입니다 — 외부에만 정산유형·계좌 컬럼이 있습니다.
"쓰다 보니 구조가 틀렸다"는 5회차에 정상적으로 일어나는 일입니다. 중요한 것은 되돌리는 절차입니다 — 추가 → 이전 → 검증 → 제거. 한 번에 바꾸고 잘못되면 복구할 방법이 없습니다.

연계 ② — 시스템 밖으로: 견적서 PDF와 메일 발송

진행 방식인접 시스템(메일·파일·알림)으로 확장합니다. 완료 기준: 시스템 안에서 만든 산출물이 밖으로 나가 실제 업무를 대체하는가.

프로세스의 맨 앞(견적)이 아직 시스템 밖에 있었습니다. 5회차에 이걸 안으로 들였습니다.

기능내용
견적서 작성고객사·품목·수량·단가 입력 → 화면에서 바로 서식 미리보기
PDF 생성화면 그대로 PDF로 변환. 견적서 화면과 프로젝트 등록 화면이 같은 생성 함수를 공용
메일 발송제목·본문 편집(기본값 자동 생성) + PDF 첨부. 발송 시각을 기록해 이력으로 남김
프로젝트 전환수주되면 견적서에서 프로젝트를 바로 생성 — 재입력 없음
견적서 목록 화면 — 고유번호·수신처·제목·금액·발송 여부·프로젝트 등록 여부 컬럼
견적서 목록. "프로젝트로 등록됐는가"를 필터로 걸 수 있어, 보낸 뒤 방치된 견적을 찾아냅니다.
견적서 상세 화면 — 수신처·품목·수량·단가·공급가액·부가세·총액과 발송 관련 필드
견적서 상세. 발송 시각이 기록되므로 "이 고객에게 언제 얼마로 보냈는지"가 시스템 안에서 추적됩니다.
D45
계산서 발행 알림은 "설정"만 만들고 실제 발송은 배포 뒤로 미뤘다

발행 요청 알림(주기·시각·담당자)을 설정 화면까지만 만들고, 실제 자동 발송은 배포 환경의 스케줄러에 붙이기로 했습니다. 로컬에서 동작하지 않는 기능을 억지로 흉내 내지 않고 경계를 명시한 것입니다.

5회차 산출물
  • 고도화 내역서 — 멤버 테이블 통합(3단계 절차와 검증 결과)
  • 연계 설계서 — 견적서 PDF·메일 발송, 알림 설정과 미구현 경계
  • 고도화·연계가 반영된 코드 + 갱신된 OVERVIEW.md

6회차 — 배포: 로그인과 보안 설정

이 회차에서 다루는 것
  • 사내 도구에 맞는 인증 방식을 고른 기준.
  • 배포 환경 제약 때문에 구조를 갈아엎은 사례를 따라갈 수 있다.
  • "정문만 잠그면 안 되는" 이유 — 우리가 겪은 뒷문.

인증 방식 선택 — 큰 프레임워크를 쓰지 않기로 했다

D43
유명한 인증 프레임워크 대신 경량 커스텀 인증을 골랐다

이유 두 가지입니다. ① 우리가 쓰는 프레임워크 버전은 변경이 많은 최신 버전이라, 내부 API에 깊이 의존하는 무거운 라이브러리는 충돌 위험이 컸습니다. ② 사용자가 사내 소수라 서명된 토큰 쿠키 + 비밀번호 해시만으로 충분·안전했습니다.

대신 이 업무에 꼭 필요한 것을 직접 넣었습니다 — 가입 승인 플로우. 가입하면 '대기' 상태로 생성되고, 관리자가 승인해야 로그인이 됩니다. 이건 범용 프레임워크의 기본 기능이 아닙니다.

관리자 사용자 관리 화면 — 이메일·이름·권한·상태와 승인·거절 조작
관리자 화면의 사용자 관리. 승인·거절·권한 변경·삭제가 되고, 마지막 관리자를 강등하거나 본인을 삭제하는 것은 막혀 있습니다. 스스로 잠기는 상황을 코드가 방지합니다.
"유명한 것"이 항상 답은 아닙니다. 선택 기준은 인기가 아니라 우리 환경과의 궁합 + 우리에게 필요한 기능입니다. 다만 인증처럼 틀리면 위험한 영역은, 직접 만들 때 검증을 훨씬 많이 해야 합니다.

배포가 막혀 구조를 갈아엎다 — 그리고 발견한 보안 구멍

진행 방식배포 대상 환경에서 실제로 빌드·동작하는지 확인합니다. 완료 기준: 로컬이 아니라 배포 환경 기준으로 통과했는가.

처음에는 요청을 가로채는 미들웨어 계층에 인증 게이트를 두었습니다. 로컬에서 7가지 시나리오 전부 통과했습니다. 그런데 배포용 빌드가 "이 방식은 지원하지 않는다"며 실패했습니다.

D43
게이팅을 미들웨어에서 레이아웃·페이지 계층으로 이전했다

조사 결과 배포 어댑터의 알려진 제약이었고 우회할 방법이 없었습니다. 그래서 인증 게이팅을 레이아웃/페이지 계층으로 옮겼습니다 — 프레임워크가 공식적으로 권장하는 보안 패턴이기도 합니다. 재검증 10/10 통과, 빌드 성공.

그런데 이 이전이 보안 구멍을 만들었습니다. 미들웨어가 없으면 서버 액션(폼 제출·데이터 변경) 요청이 레이아웃의 인증 검사보다 먼저 실행됩니다. 즉 로그인하지 않아도 데이터를 바꿀 수 있는 상태였습니다.

조치: 데이터를 변경하는 서버 액션 46개 전부의 첫 줄에 인증 확인을 삽입했습니다. 수작업이 아니라 코드 변환 스크립트로 처리하되, 함수 시그니처를 잘못 건드리지 않도록 괄호 짝을 세어 정확한 위치를 찾았습니다.

정문을 옮기면 뒷문이 열립니다. 화면 접근만 막고 데이터 변경 경로를 막지 않는 것이 이 단계의 대표적 사고입니다. 점검 방법은 간단합니다 — 로그인하지 않은 상태로 저장 요청을 직접 보내 보는 것. 화면이 안 열린다고 안전한 게 아닙니다.
6회차 산출물
  • 운영 접속 경로와 계정 발급 절차 — 가입 → 관리자 승인
  • 보안 점검 보고 — 비로그인 차단·권한 구분·위조 쿠키 차단·서버 액션 46개 가드
  • 배포 구성 문서 — 환경변수, 빌드 제약과 우회 결정
6회차를 끝낼 때 확인한 것
  • 비로그인 상태에서 화면 접근이 차단된다.
  • 비로그인 상태에서 데이터 변경 요청도 차단된다.
  • 권한(일반/관리자) 구분이 실제로 동작한다.
  • 위조된 인증 정보가 거부된다.
  • 관리자가 스스로를 잠글 수 없다(마지막 관리자 보호).

7회차 — 안정화와 문서 체계

이 회차에서 다루는 것
  • 부하 시험 결과를 어떻게 해석했는가.
  • 시스템 설명서와 사용자 매뉴얼의 역할 차이.
  • 개발 중에 쌓은 기록이 어떻게 문서가 되었는가.

부하 시험 — 숫자보다 해석이 중요하다

배포 전 상태로 동시 요청을 부어 봤습니다.

0
동시 50 캘린더 요청 중 에러
0
동시 80 지급 요청 중 에러
9ms
단일 요청 최소 응답
2.8s
동시 요청 시 지연

에러는 0인데 동시 요청 지연이 2.8초로 튀었습니다. 여기서 "느리니 최적화하자"로 가지 않은 것이 이 회차의 핵심입니다. 원인을 먼저 봤습니다 — 당시 로컬 DB가 요청을 한 줄로 세워 처리하는 종류였기 때문이고, 운영 환경의 DB로 바꾸면 사라지는 성질의 지연이었습니다.

측정값은 측정 환경과 함께 해석해야 합니다. 로컬 환경의 특성 때문에 생긴 숫자를 코드 문제로 오해하면, 있지도 않은 병목을 최적화하느라 회차를 날립니다. 대신 "운영 DB 전환 후 재측정"을 후속 과제로 적어 두었습니다.

함께 넣은 것은 기본 보안 헤더입니다 — 서버 종류를 알리는 헤더 제거, 콘텐츠 타입 추측 금지, 다른 사이트에 프레임으로 삽입 금지, 참조 주소 전달 제한. 코드 몇 줄이지만 안 하면 지적받는 항목입니다.

문서 4종 — 개발 중에 쌓인 기록에서 그대로 나왔다

진행 방식시스템 설명서와 사용자 매뉴얼을 작성해 사용자가 찾을 위치에 둡니다. 완료 기준: 다음 사람이 이 문서만 보고 인수할 수 있는가.

문서담는 것독자
OVERVIEW.md시스템 소개, 설치·실행 방법, 알려진 한계다음 개발자·인수자
설계 문서배경·목적, 데이터 모델, 화면·기능, 권한, 이관 규칙, 미결 항목구조를 이해해야 하는 사람
배포 가이드환경변수, 배포 절차, 스키마 변경 순서, 제약운영자
결정 로그무엇을 왜 그렇게 정했는가 — 되돌린 결정 포함"왜 이렇게 돼 있지?"를 묻는 모든 사람
이 문서들은 7회차에 몰아서 쓴 것이 아닙니다. 설계 문서는 1회차의 개발계획서가 자란 것이고, 결정 로그는 2회차부터 매 회차 덧붙인 것입니다. 7회차에 한 일은 정리와 갱신이었습니다. 몰아서 쓰려 했다면 "왜 그렇게 정했는지"는 이미 기억에서 사라졌을 것입니다.
7회차 산출물
  • 안정화 점검 보고 — 부하 결과와 해석, 후속 과제 4건
  • 시스템 설명서 3종 — OVERVIEW.md · 설계 · 배포 가이드
  • 사용자 매뉴얼 — 화면 캡처와 업무 시나리오 위주

8회차 — 오픈 베타: 운영 환경으로 옮기고 실제로 쓰기 시작한다

이 회차에서 다루는 것
  • 로컬 전용 구현이 운영 환경에서 왜 깨졌는가.
  • 로컬과 운영 데이터를 안전하게 오가기 위해 만든 것.
  • 실사용 전환에 앞서 무엇을 추적 가능하게 만들어 두었는가.

로컬에서만 되던 것들을 고친다

진행 방식운영 환경으로 옮기고 실사용을 시작합니다. 완료 기준: 로컬 가정에 기대던 코드가 전부 걷혔는가.

실사용으로 넘어가면서 두 가지가 걸렸습니다. 둘 다 "로컬에서는 됐는데" 유형입니다.

로컬 가정운영에서 깨지는 이유조치
DB가 파일 하나동시 접속·확장이 안 되고, 배열·열거형 등 표현력 제약운영급 DB로 통일 — 로컬도 같은 종류로 맞춰 "배포 시 무변경"
첨부파일을 서버 폴더에 저장배포 환경은 파일을 쓸 수 있는 디스크가 없음객체 스토리지로 이전. 비공개 버킷 + 서명된 임시 URL로만 열람
D44
로컬과 운영의 DB 종류를 통일했다

"로컬은 가볍게, 운영은 제대로"라는 이중 구성을 유지할지 고민하다가 통일을 택했습니다. 이중 구성은 스키마 파일과 접속 코드가 갈라지고, 배포할 때마다 바꿔야 합니다. 통일하면 배포 시 변경 사항이 0이 됩니다.

데이터 이전은 내보내기 → 가져오기로 1,382행 전부 옮겼고, 건수·날짜 경계·금액 합계·구조화 필드를 대조해 검증했습니다.

첨부파일 저장소 이전은 왕복 검증까지 했습니다 — 버킷 생성(비공개) → 업로드 → 서명 URL로 조회 성공 → 서명 없이 직접 접근하면 차단 → 정리. 마지막 항목이 핵심입니다.

저장소를 옮겼으면 "막혔는지"까지 확인하십시오. 업로드와 조회가 되는 것은 절반입니다. 권한 없는 접근이 실제로 거부되는지를 확인하지 않으면, 사업자등록증 같은 문서가 URL만 알면 열리는 상태로 운영에 올라갑니다.

로컬 ↔ 운영 동기화 — 실수를 막는 기본값

실사용이 시작되면 운영 데이터가 진짜가 됩니다. 그런데 대량 수정은 로컬에서 하는 게 편합니다. 그래서 세 개의 명령을 만들었습니다.

동기화 명령과 안전장치
diff   운영 ↔ 로컬 차이(추가·삭제·변경 건수)만 보여 줌 — 읽기 전용
pull   운영 → 로컬로 가져오기
push   로컬 → 운영으로 올리기

※ --force 를 붙이지 않으면 세 명령 모두 "미리보기"만 실행된다.
   실제로 쓰는 동작은 명시적으로 요청해야만 일어난다.
위험한 명령의 기본값을 안전한 쪽으로 두는 것 — 이것이 운영 사고를 막는 가장 값싼 방법입니다. "실수로 운영 데이터를 덮어썼다"는 사고는 대개 기본값이 실행이었기 때문에 생깁니다.
대시보드 화면 — 계약총액·발행완료·미청구·미수금 지표와 단계별 파이프라인
실사용 전환 시점의 대시보드. 계약총액·발행완료(매출)·미청구·미수금이 한 화면에 있습니다. 미청구는 "수행은 했는데 계산서를 안 끊은 것"을 드러내는 지표로, 이 시스템이 만든 새 관리 포인트입니다.
8회차 산출물
  • 오픈베타 전환 계획·공지 — 전환일, 기존 도구 사용 중단 시점
  • 운영 전환 기록 — DB 이전 1,382행 검증, 저장소 이전 왕복 검증
  • 동기화 도구와 사용 규칙 — diff/pull/push, 기본값=미리보기

9회차 — 실사용 데이터가 시키는 개선

이 회차에서 다루는 것
  • 실사용에서만 드러나는 종류의 문제를 예로 들 수 있다.
  • 용어 혼동을 DB 단위로 정리한 방법.
  • 날짜·시간대처럼 조용히 틀리는 버그를 어떻게 잡았는가.

개선 ① — 이름이 헷갈리면 데이터도 헷갈린다

진행 방식실사용에서 나온 개선 요청을 데이터로 우선순위를 매겨 처리합니다. 완료 기준: 개선의 근거가 "요청받아서"가 아니라 "이렇게 쓰이고 있어서"인가.

쓰다 보니 같은 단어가 두 뜻으로 읽히는 문제가 드러났습니다. '수금유형'을 뜻하는 필드 이름이 '지급'으로도 읽혀서, 화면과 코드 양쪽에서 혼선이 났습니다.

D45
용어를 DB 테이블·컬럼 이름 단위로 리네임했다

화면 라벨만 고치는 대신 테이블명·컬럼명·경로까지 바꿨습니다. 화면만 고치면 코드를 읽는 사람이 계속 헷갈리기 때문입니다.

방법: 단일 트랜잭션 안에서 이름만 변경(데이터 이동 없음)해 로컬과 운영에 동일하게 적용하고, 적용한 SQL을 파일로 남겨 기록했습니다. 데이터는 그대로 보존됩니다.

이름 바꾸기는 기능 추가보다 값이 큽니다. 특히 우리 시스템처럼 돈이 오가는 방향(받는 돈/주는 돈)이 섞이면 실수가 곧 금액 오류입니다. 9회차는 이런 "조용한 개선"을 하기 좋은 자리입니다.

개선 ② — 하루가 밀리는 버그

캘린더에서 어떤 강의가 전날 칸에 나타나는 일이 있었습니다. 원인은 시간대였습니다 — 날짜를 저장할 때 한국 시각 자정이 세계 표준시로는 전날이 됩니다. 서버가 표준시로 계산하면 하루가 밀립니다.

D41 · D45
날짜 계산을 한국 시간대로 명시 고정했다

날짜를 다루는 유틸리티 전체를 한국 시간대 기준으로 고정하고, 비교 기준도 마찬가지로 맞췄습니다. 특히 월별 집계 경계(7월 1일 강의가 6월로 잡히는 문제)에서 "가짜 중복"처럼 보이는 착시가 있었습니다 — 데이터가 잘못된 게 아니라 비교 기준이 잘못된 것이었습니다.

날짜 버그는 조용합니다. 에러가 나지 않고, 값이 그럴듯하며, 대부분의 날짜는 맞습니다. 월말·월초와 자정 근처에서만 틀립니다. 경계값으로 검증하지 않으면 실사용 몇 달 뒤에 "이상한데?"로 발견됩니다.
관리자 실적 화면 — 멤버별 강의 일수·건수·시간·강사료 집계 표
관리자 실적 화면. 개인별 강의 일수·건수·강사료가 집계됩니다. 날짜와 시간대가 정확해야 이 표가 의미를 갖습니다 — 하루가 밀리면 월별 실적이 통째로 틀립니다.
9회차 산출물
  • 개선 백로그 — 실사용에서 나온 항목과 우선순위 근거
  • 개선 리포트 — 용어 리네임(적용 SQL 보관), 시간대 고정, 화면 필터 재설계
  • 지표 추세 — 미청구·미수금이 실제로 줄고 있는가

10회차 — 완성, 효과 정량화, 그리고 발표

이 회차에서 다루는 것
  • "최종 완성"을 이슈 0이 아니라 어떤 상태로 정의했는가.
  • 실패한 선택을 인정하고 갈아탄 판단.
  • 개발 중 기록이 어떻게 최종 발표 자료가 되었는가.

끝까지 안 되는 것은 갈아탄다

진행 방식최종 시스템을 완성하고 효과를 정량화합니다. 완료 기준: 남은 이슈가 "무엇인지 아는 상태"로 정리됐는가.

D46
배포 플랫폼을 바꿨다 — 6회차부터 붙잡던 것을 놓았다

6회차에서 게이팅 구조까지 바꿔 가며 맞췄던 배포 어댑터가 끝내 안정적으로 빌드되지 않았습니다. 10회차에 다른 플랫폼으로 전환했습니다.

전환은 제거와 유지를 명확히 나눴습니다 — 제거: 이전 플랫폼 전용 설정·스크립트·의존성(패키지 259개 감소)과 주석 속 언급까지. 유지: DB 접속 어댑터(새 환경에서도 그대로 동작). 앱이 환경변수로만 설정을 읽고 있었기 때문에 애플리케이션 코드는 변경 불필요했습니다.

결정 로그에 이렇게 적었습니다 — "이후 배포는 새 플랫폼 기준. 다음 작업 때 헷갈리지 말 것."

6회차에 내린 결정이 10회차에 뒤집힐 수 있습니다. 중요한 것은 얼마나 빨리 인정하는가, 그리고 갈아탈 때 흔적을 남기지 않는가입니다. 주석에 남은 옛 플랫폼 이름 하나가 다음 사람을 한나절 헤매게 만듭니다.
이 전환이 가능했던 이유는 1회차 설계에 있습니다. 앱이 특정 플랫폼의 API에 직접 기대지 않고 환경변수로만 설정을 읽도록 만들어 두었기 때문에, 플랫폼을 바꿔도 코드를 고칠 필요가 없었습니다. 초기 설계의 값은 이런 순간에 드러납니다.

효과 정량화 — 무엇이 얼마나 좋아졌는가

발표에서 답해야 할 질문은 하나입니다. "무엇이 얼마나 좋아졌는가." 우리의 답은 세 갈래였습니다.

구분BeforeAfter
사라진 작업영업 성사 시 시트·에어테이블 양쪽에 이중 등록한 곳에 입력하면 캘린더·프로젝트·집계에 자동 반영
사라진 작업외부강사 지급 시 엑셀에서 수식 복사·행 추가강의가 생기면 지급행 자동 생성, 소득구분 선택 시 원천징수 자동 계산
새로 가능해진 것"이 프로젝트 미청구액"을 알 방법이 없음계약총액 − 발행완료 = 미청구가 상시 표시. 발행 누락이 드러남
새로 가능해진 것개인별 강의 실적을 손으로 세야 함일수·건수·시간·강사료가 자동 집계(관리자 권한)
품질셀 노트 속 시간·장소는 사람만 읽음구조화되어 검색·집계 가능 (복원 532건)
3 → 1
쓰는 도구 수
(시트·에어테이블·엑셀 → 시스템)
89%
과거 데이터 자동 연결률
(나머지는 화면에서 배정)
100%
원천징수 계산 일치
(지급대장 전 행 대조)
46
문서화된 결정
(되돌린 결정 포함)
"이슈 0"이 완성이 아닙니다. 우리도 미결 항목을 남겼습니다 — 일부 프로젝트의 금액 미확정, 스캔본에서 추출 못 한 사업자번호, 운영 DB 전환 후 부하 재측정. 완성이란 남은 이슈가 무엇인지 아는 상태이고, 그 목록이 문서에 있는 상태입니다.
10회차 산출물
  • 최종 코드 + 최종 OVERVIEW.md — 알려진 한계 포함
  • 효과 평가서 — Before/After와 측정값
  • 개발 히스토리 — 결정 로그 D1~D46을 시간순으로
  • 문서·매뉴얼 개정본 · Lessons Learned
  • 최종 발표 자료

Lessons Learned — 우리가 남긴 다섯 문장

#교훈어디서 나왔나
1최소 단위를 먼저 정하라"강의 1건 = 하루 × 강사 1명"이 개인 실적·정산·캘린더를 전부 지탱했다 1회차
2목업보다 실데이터가 빠르다순서를 바꿔 이관을 앞당긴 덕에 화면을 두 번 만들지 않았다 2회차
3돈 계산은 원본과 전수 대조하라원 단위 내림으로 잘못 구현했던 원천징수를 36행 대조로 잡았다 3회차
4정문을 옮기면 뒷문을 다시 보라게이팅 이전 후 서버 액션이 무방비였다 — 46개에 가드 삽입 6회차
5결정을 그때그때 적어라7회차의 문서 4종이 몰아쓰기 없이 나왔다. 되돌린 결정이 특히 값졌다 전 회차

부록 — 결정 로그로 본 10회차

우리 개발 노트에 남은 결정 46건을 회차에 대응시킨 표입니다. 어느 과제든 이 형태로 쌓아 두면, 10회차의 문서·발표가 저절로 만들어집니다.

회차결정 번호대표 결정
1~2회차D1 ~ D13스택 확정 · 배열/열거형을 단순 타입으로 저장 · 개발 순서 변경(실데이터 우선) · 이관을 초안 뼈대로 끊기 · 드래그 이동은 낙관적 갱신
3회차D6 · D16 · D17원천징수 수식 재구현·36행 대조 · 이관 전면 재작성으로 연결률 89% · 셀 노트 1,022개 복원
4회차D14 ~ D25데이터 페이지 동적 렌더 · 스프레드시트 라이브러리 미채택(UX 일관성) · 인라인 편집 · 헤더/합계 고정
5회차D26 ~ D42멤버 테이블 물리 통합(3단계) · 프로젝트 상세를 저장 버튼 1회 방식으로 · 지급대장 양식 전면 개편 · 견적서·메일·PDF
6회차D43경량 커스텀 인증 · 가입 승인제 · 미들웨어 → 레이아웃 게이팅 전환 · 서버 액션 46개 인증 삽입
7회차D43(4단계)부하 시험(에러 0, 지연은 로컬 DB 특성) · 보안 헤더 · 후속 과제 4건 명시
8회차D44로컬·운영 DB 통일 · 첨부파일 객체 스토리지 이전(왕복 검증) · 동기화 도구(기본값=미리보기)
9회차D45용어 리네임(DB 단위) · 날짜 한국 시간대 고정 · 캘린더 필터·되돌리기 · 대시보드 개편
10회차D46배포 플랫폼 전환 — 실패 인정, 흔적까지 제거, 기준을 로그에 못박음
다른 과제에 옮겨 볼 것
  • 우리 과제의 "1건 = 무엇"에 해당하는 최소 단위를 한 문장으로 쓸 수 있다.
  • 서로 단위가 다른 데이터를 억지로 한 테이블에 넣고 있지 않다.
  • 품질 지표가 "100%여야 하는 것"과 "높을수록 좋은 것"으로 나뉘어 있다.
  • 결정 로그를 이미 쓰기 시작했다(회차마다 한 줄이라도).
  • 자동화가 못 미친 잔여분을 사람이 화면에서 처리할 통로가 있다.