기술

반품은 주문 화면에 없습니다 — 매입 집계가 277만 원 부풀었던 이유

2026.09.0310분 읽기

프랜차이즈 ERP를 운영하던 중 연락을 받았습니다. 전기비교 화면의 매입 금액이 외부 식자재 공급사에서 받은 원본과 다르다는 것이었습니다. 금액이 안 맞는다는 신고는 대개 계산식 문제로 접수됩니다. 저도 처음엔 합계 로직이나 기간 필터부터 의심했습니다.

결론만 먼저 적으면, 계산식은 멀쩡했습니다. 3주 기준으로 전사 매입 집계는 2,776,753원 과대였고, 원인은 집계가 읽고 있던 테이블 그 자체였습니다. 이 신고 하나가 이틀에 걸친 집계 원천 전면 교체로 번졌습니다.

총계가 안 맞을 때 계산 로직과 모집단을 먼저 분리해서 봐야 한다는 이야기는 예전에도 한 번 정리했습니다. 이번 건은 그 분리를 한 단계 더 앞으로 밀어낸 사례입니다. 모집단을 만드는 원천 뷰 자체가 특정 종류의 거래를 담을 수 없는 구조였습니다.


매입 집계가 공급사 주문내역만 읽고 있었습니다

매입 집계 쿼리의 원천은 공급사 주문내역 테이블이었습니다. 주문내역은 담당자가 매일 들여다보는 화면의 원천이라 익숙하고, 품목·수량·금액이 다 들어 있습니다. 같은 거래를 회계 관점으로 적어 두는 거래원장 테이블은 아예 참조하지 않고 있었습니다.

두 테이블은 평범한 날에는 같은 숫자를 냅니다. 그래서 이 구조가 몇 해 동안 문제로 드러나지 않았습니다. 확인 작업은 쿼리 한 줄로 끝났습니다. 주문내역 99만 행에서 금액이 음수인 행을 세어보니 0건이었습니다.


집계 원천을 고를 때 첫 질문은 반대 방향 거래의 존재입니다

집계 원천을 고를 때 첫 질문은 "이 뷰에 반대 방향 거래가 존재하는가"입니다. 주문내역 99만 행에 음수가 단 한 건도 없었다는 사실은 데이터 품질 이야기가 아니라 유효한 주문건에 대한 이야기입니다. 반품과 매출이관이 그 화면엔 원천적으로 없으니, 그 뷰로 만든 집계는 구조적으로 과대입니다.

주문내역은 주문서를 모아 둔 서류함이고, 거래원장은 통장입니다. 서류함에는 취소된 주문서를 찢어서 다시 끼워 넣지 않습니다. 반면 통장에는 입금과 출금이 같은 줄에 남고, 되돌려받은 돈도 한 줄을 차지합니다. 주문·발주·신청처럼 요청을 담는 뷰는 대개 양수만 사는 세계이고, 반품·취소·조정은 요청이 아니라 사후 사건이라 요청 뷰로 되돌아오지 않습니다.

127-1.jpg


원인 규명의 종결 기준은 등식입니다

차이 금액을 확인한 뒤 바로 코드를 고치지 않고, 신고 차이가 원인 항목의 합과 정확히 맞아떨어지는지부터 확인했습니다. 3주 기준 과대분 2,776,753원은 반품환입 -2,365,613원(69행), 매출이관 +238,250원(5행), 미납품 14행으로 완전히 분해됐습니다.

이 등식이 맞을 때까지가 분석입니다. "대충 그 정도 차이"에서 멈추면 남은 오차 안에 아직 못 본 원인이 한 건 더 살아 있을 수 있습니다. 실제로 이번에도 그랬습니다.


3건을 한 원인으로 묶지 않았습니다

문의는 총 3건이었고, 그중 2건은 위의 구조 결함이었습니다. 남은 한 건은 다른 공급사의 전표 소급입력과 수집 시차가 원인이었습니다. 저녁에 해당 구간을 재수집하니 1,104,581원이 정확히 일치하면서 자동으로 해소됐습니다. 이 건을 같은 원인으로 뭉쳤다면 멀쩡한 코드를 고쳤거나, 반대로 구조 결함을 시차 문제로 덮었을 것입니다.


보정과 일괄 전환 대신 2단계 전환을 골랐습니다

옵션

방식

걸림돌

금액 보정

차이만큼 조정값을 넣어 화면 숫자를 맞춘다

다음 반품에서 또 어긋난다. 구조 결함은 그대로 남는다

일괄 전환

매입을 읽는 모든 화면을 한 배포로 교체한다

파급 범위 검증 없이 회계 숫자 전체가 한 배포에 걸린다

2단계 전환(채택)

신고 화면부터 좁게 바꿔 등식으로 닫고, 이후 잔여를 전면 전환한다

배포가 두 번 나뉜다. 대신 각 단계가 실측으로 닫힌다


전환의 종결 조건을 구 원천 참조 0으로 걸었습니다

1단계에서는 거래원장과 매장 브리지를 묶은 공용 조인 조각을 신설하고, 신고가 들어온 전기비교 계열 쿼리만 그 조각으로 갈아탔습니다. 2단계에서는 매입률, 매장 금액 팝업, 일·주·월 물류합계, 매입현황과 이상탐지, 발주GAP, 품목 동기화 배치까지 남은 소비처를 전부 옮겼습니다.

이때 종결 조건을 "주요 화면은 바꿨다"로 두지 않고 백엔드 매퍼에 구 주문내역 쿼리 0건으로 걸었습니다. 앞의 문장은 상태가 아니라 소감이지만, 뒤의 문장은 grep 한 번으로 확인되는 상태입니다. 원천 전환처럼 조용히 반쯤 끝나기 쉬운 작업은 검증 가능한 상태를 종결 조건으로 써야 나중에 남은 절반을 사고로 만나지 않습니다.

예외도 남겼습니다. 발주GAP은 화면 목적상 주문 시점의 공급가가 맞는 지표라서 금액 필드의 의미는 유지하고 소스만 교체했습니다. 전면 전환이라는 말에도 의미 검토라는 예외 절차는 남습니다.

127-2.jpg


플랜 문구보다 참조 그래프가 진실이었습니다

2단계 계획서에는 특정 쿼리 3개가 "매입률 화면 전용"이라고 적혀 있었습니다. 코드 리뷰에서 참조 그래프를 훑자 그 3개가 실제로는 5개 서비스에서 공용으로 호출되고 있었습니다. 계획서 문장은 작성 시점의 이해였을 뿐이고, 파급 범위는 참조로만 확정됩니다.

잘못된 도메인 가정이 데이터를 조용히 갈라놓는 사례는 이 시스템에서 처음이 아니었습니다. 문서에 적힌 가정을 그대로 믿고 범위를 잡으면, 검증 대상에서 빠진 소비처가 배포 후에 숫자로 항의합니다.


음수가 처음 흘러들어오면 기존 부등호를 전수 점검해야 합니다

1단계 리뷰에서 잡힌 지적이 이번 작업의 가장 값싼 수확이었습니다. 어떤 화면의 미주문 판정이 금액 조건 <= 0 이었습니다. 원천이 주문내역일 때는 음수가 존재하지 않았으니 그 조건은 사실상 == 0 과 같았습니다. 원천이 거래원장으로 바뀌면서 반품 순액이 음수로 들어오자, 반품이 발생한 품목이 화면에 "미주문"으로 렌더되기 시작했습니다.

조건을 == 0 으로 정정하면 끝나는 한 줄짜리 수정이지만, 이런 부등호는 코드베이스 곳곳에 흩어져 있습니다. 원천 전환은 테이블 교체가 아니라 값의 범위 전환이고, 새 범위가 들어오면 그 값을 비교하던 조건들의 의미가 같이 바뀝니다.

표시 정책도 같은 자리에서 정했습니다. 반품 순액으로 음수가 된 매입은 0으로 눌러 보여주거나 절댓값을 취하지 않고, 음수 그대로 표시하기로 했습니다. 회계 숫자를 보기 좋게 만드는 순간 대사가 불가능해집니다. 정책을 정한 뒤 5개 서비스를 실제 API로 호출해 음수 처리를 점검했고, Infinity와 NaN 없이 계산이 온전함을 확인했습니다.

127-3.jpg


검증 수치와 배포 순서

회계 숫자를 건드리는 전환이라 검증은 화면 눈대중이 아니라 실측으로 남겼습니다.

회귀 확인: 대표 3개 값을 실제 API로 호출해 전환 전후 일치 확인

브리지 무손실: 거래원장과 매장 매핑 20,161 / 20,215 통과, 미통과분은 비영업 조직으로 정상

전환 충격: 전사 -0.33%(1단계), 월별 -0.2~0.5%(2단계)

누락 확인: 매장 211곳 전후 동일, 집계에서 사라진 매장 0곳

배포 순서 자체가 정합성 조건이기도 했습니다. 개명된 매장 코드를 승계하고 미연결 매장에 알람을 띄우는 수집기가 먼저 나가야 했습니다. 백엔드를 먼저 내보내면, 코드가 아직 연결되지 않은 신규 매장이 알람도 없이 집계에서 빠지는 시간 창이 생깁니다. -0.33%는 이 도메인의 결과일 뿐이라, 반품 비중이 큰 곳이면 전환 자체가 큰 숫자 변화이니 사전 공지가 작업의 일부가 됩니다.


다음에 같은 신고를 받으면 확인할 것

이 집계의 원천 뷰에 반대 방향 거래(반품·취소·이관)가 한 건이라도 존재하는지 카운트 쿼리로 확인한다

화면의 목적이 회계인지 속보인지 정하고 원천을 그 목적에 맞춘다. 원장이 지연 갱신되는 시스템이라면 속보 화면은 요청 뷰가 맞을 수 있다

신고 차이 금액이 원인 항목의 합과 등식으로 맞아떨어지는지 확인하고, 안 맞으면 아직 못 찾은 원인이 있다고 본다

전환 종결 조건을 grep으로 검증되는 상태(구 원천 참조 0건)로 적고, 계획서 문구 대신 참조 그래프로 파급 범위를 정한다

새 원천에서 음수가 유입되면 금액을 비교하는 모든 부등호와 표시 로직을 전수 점검한다

이 작업의 실제 산출물은 바뀐 쿼리보다 문서로 남은 세 가지였습니다. 음수 매입을 그대로 표시한다는 표시 정책, 수집기를 먼저 배포한다는 순서 규칙, 그리고 회계 집계의 원천은 거래원장이라는 도메인 공리입니다. 담당자가 처음 들고 온 것은 화면 하나의 금액 차이였지만, 그 화면을 고치는 가장 짧은 길이 원천 교체였습니다.

#운영의기술#데이터집계#거래원장#원천전환#회귀검증#코드리뷰#SM운영