미수금 카드를 눌렀는데 내역을 더한 값이 다르면 대표는 어느 쪽을 믿어야 할까. 나는 공개 체험의 숫자도 설명용이라는 이유로 따로 맞춰 넣지 않기로 했다.

이번 글은 합성 거래에서 카드와 내역이 만들어지는 과정을 따라간다. 이어서 새로 만든 독립 검증 모듈을 연결해 금액 누락과 초과 수금을 어떻게 드러낼지 살펴본다. 실제 회사의 장부나 회계 정책을 공개하는 글은 아니다.

멋진 숫자를 먼저 정하면 두 번째 원본이 생긴다

체험의 월간 예시는 매출 세 행과 매입 두 행으로 시작한다. 합성 매출은 2400만원, 1800만원, 600만원이며 각 행에 수금 내역이 붙는다. 첫 행은 1000만원과 680만원을 나누어 받은 것으로 정했다. 금액 카드에 4800만원을 직접 쓰고 내역을 별도로 꾸미는 대신, 이 행들의 합에서 카드를 만들었다. 요약을 수정하려고 다른 곳의 숫자를 다시 고칠 필요가 없게 한 것이다.

수금 합은 3540만원이고 미수금 합은 1260만원이다. 첫 행의 미결액 720만원과 두 번째 행의 540만원이 합계와 이어진다. 이 금액은 실제 매출이나 현금 흐름을 증명하는 수치가 아니라 공개 체험의 계산 관계를 읽기 위한 값이다. 나는 합성이라는 설명과 행별 계산을 함께 두어 숫자의 출처를 추적할 수 있게 했다.

미수금 내역은 다른 장부를 다시 입력한 결과가 아니다

현재 모델은 같은 매출 행에서 수금 합과 잔액을 계산한다. 매출을 열면 전체 행을, 미수금을 열면 잔액이 양수인 행을 보여 준다. 행을 새로 타이핑해 별도의 미수 장부를 만드는 방식이 아니다. 매입과 미지급도 같은 방향으로 파생한다. 이 구조에서는 대표 카드와 내역의 불일치를 별도 화면의 입력 실수로 늘릴 이유가 줄어든다.

아래 함수는 현재 공개 합성 모델에서 가져왔다. 원본 행을 만드는 syntheticLedger 함수에 의존하는 발췌이며 이것만으로 독립 실행되지는 않는다. 음수·누락 등 임의의 입력을 받는 범용 검증 함수도 아니다. 닫힌 합성 자료를 표시하기 위한 원래 함수와 이번에 별도로 공유한 입력 검증기의 책임을 구별하는 것이 재사용할 때 중요하다.

코드와 구현 살펴보기

현재 합성 모델의 파생 내역 함수 · 원본 함수에 의존

export function ledgerRows(key){
  if(!['sales','purchases','receivables','payables'].includes(key))throw Error('Unknown example ledger');
  const ledger=syntheticLedger(),source=['sales','receivables'].includes(key)?ledger.sales:ledger.purchases;
  return source.map(row=>({...row,settled:row.receipts.reduce((n,v)=>n+v,0),balance:row.amount-row.receipts.reduce((n,v)=>n+v,0)})).filter(row=>!['receivables','payables'].includes(key)||row.balance>0);
}

받을 돈과 연체된 돈을 같은 말로 부르지 않았다

기준일은 합성 날짜인 2030년 4월 15일이다. 첫 매출 행은 기한이 4월 8일이고 720만원이 남아 있다. 두 번째 행은 4월 17일이 기한이므로 기준일에는 아직 기한이 오지 않았지만 540만원의 미결액이 있다. 따라서 미수금 1260만원 전체를 연체라고 부르면 의미가 틀어진다. 카드 제목과 보조 설명은 계산식만큼이나 날짜의 범위를 정확히 가리켜야 한다.

월간 자료는 4월 1일부터 15일까지의 합성 예시라는 범위를 갖는다. 실제 업무에서는 전기 이월, 선수금, 환불과 상계, 세금과 매출 인식 정책을 함께 정해야 한다. 여기서 보여 주는 단순 잔액 계산을 그런 정책의 완성으로 내세우지 않는다. 대표에게 필요한 숫자가 무엇인지 먼저 합의한 다음 그 숫자가 포함하는 거래와 날짜를 정하는 순서를 남기고 싶었다.

보이는 단위와 계산하는 단위를 분리했다

원본과 계산은 정수 원화로 유지한다. 한국어 표시는 만원으로 바꾸고 영어 표시는 원화 기호와 자릿수를 붙이지만, 화면에 보이는 문자열을 다시 읽어 더하지 않는다. 1260이라는 표시만 떼어 놓으면 원인지 만원인지 알 수 없고, 반올림된 문자열의 합은 원본 합과 달라질 수 있다. 단위는 화면에서 설명하고 계산의 자료형은 모델에서 고정했다.

자바스크립트의 숫자는 모든 크기의 정수를 정확히 다루지는 않는다. 이번 독립 모듈은 각 금액과 부분 수금이 음수가 아닌 안전한 정수인지 먼저 확인하고, 행의 수금 합과 전체 합에서도 안전 범위를 확인한다. 문자열 숫자나 소수 금액을 임의로 바꾸어 받지 않는다. 여러 통화나 소수 단위가 필요하면 그 단위 계약부터 바꾸어야 하며 표시 형식만 바꾸어 해결할 수 없다.

누락을 0으로 만들면 합계는 맞아도 판단이 틀릴 수 있다

금액이 없는 행을 0으로 채우면 모든 합계가 계산되는 것처럼 보인다. 하지만 아직 확인하지 않은 금액과 실제 0원은 서로 다르다. 새 검증 모듈에서는 금액 누락, 수금 목록 누락, 중복 거래 식별자, 초과 수금과 정수 범위 초과가 있으면 전체 합계를 null로 돌려준다. 문제의 행과 이유는 남기고, 완성된 장부인 것처럼 표시하지 않는다.

초과 수금을 잔액 0으로 잘라내지 않은 이유도 같다. 실제 업무에서는 선수금이나 잘못된 배분일 수 있으므로 별도의 판단이 필요하다. 이 단순 모듈은 어느 정책이 맞는지 대신 결정하지 않고 검토 사유로 돌려준다. 일부 입력만 정상이라면 그 항목들의 합을 전체 합계로 표시해서는 안 된다. 문제가 해결되면 같은 원본을 다시 계산한다.

숫자를 확인하는 기준도 공유한다

합성 정상 자료에서는 각 행의 원금이 수금과 잔액의 합인지, 전체에서도 같은 관계인지 확인했다. 독립 패키지에는 누락·중복·초과 수금·안전 정수 경계를 시험하는 테스트가 들어 있다. 작은 원금과 부분 수금 조합 861개도 계산해 잔액을 대조했다. 이것은 합성 정수 모델의 검증이며 실무 회계의 적정성이나 모든 업무 자료와의 호환성을 뜻하지 않는다.

공유 모듈 페이지에서 정상 자료와 오류 자료를 바꾸어 보고 코드·테스트·한영 데모가 담긴 ZIP을 받을 수 있다. 직접 적용할 때는 거래 식별자, 금액 단위, 기한, 수금 배분 규칙을 자신의 회사에 맞춰 명시해야 한다. 나는 대표 화면이 한눈에 보이는 것만큼 그 숫자를 다시 풀어 확인할 수 있는 구조도 회사 맞춤 ERP의 중요한 부분이라고 본다.