정산 가능한 청구 원장 스키마: 인보이스와 결제
인보이스, 결제, 크레딧, 조정을 분리해 재무가 합계와 변경 내역을 쉽게 재구성·감사할 수 있는 정산 가능한 청구 원장 스키마 설계 방법을 알아보세요.

청구 데이터가 정산되지 않는 이유
재무에서 “정산”은 간단한 약속입니다: 보고서의 총합이 원본 레코드와 일치하고, 모든 숫자는 추적 가능해야 합니다. 한 달에 $12,430이 수집되었다면, 정확히 어떤 결제(및 환불)가 그 합을 만들었는지 지목할 수 있어야 하고, 날짜가 있는 기록으로 모든 차이를 설명할 수 있어야 합니다.
청구 데이터는 대개 결과를 저장하고 사실을 저장하지 않을 때 정산을 멈춥니다. paid_amount, balance, 또는 amount_due 같은 열은 애플리케이션 로직에 의해 시간이 지나며 업데이트됩니다. 하나의 버그, 한 번의 재시도, 혹은 수동 “수정”으로 과거 기록이 조용히 바뀔 수 있습니다. 몇 주 후에는 인보이스 테이블이 송장을 “paid”로 표시하지만 결제 행이 합쳐지지 않거나, 매칭되는 크레딧 없이 환불이 존재할 수 있습니다.
또 다른 흔한 원인은 서로 다른 문서 유형을 섞어 버리는 것입니다. 인보이스는 결제가 아닙니다. 크레딧 메모는 환불이 아닙니다. 조정(adjustment)은 할인과 동일하지 않습니다. 이러한 문서들이 많은 선택적 필드를 가진 하나의 “transactions” 행에 밀어 넣어지면 보고는 추측이 되고 감사는 논쟁이 됩니다.
근본적인 불일치는 간단합니다: 애플리케이션은 종종 현재 상태(“접근이 활성화되어 있나?”)를 신경 쓰는 반면, 재무는 추적 기록(“무슨 일이, 언제, 왜 일어났나?”)을 신경 씁니다. 청구 원장 스키마는 둘을 모두 지원해야 하지만, 추적 가능성이 우선이어야 합니다.
다음 결과를 목표로 설계하세요:
- 고객별, 인보이스별, 회계 기간별로 명확한 총합
- 모든 변경은 새 행으로 기록(덮어쓰기 금지)
- 인보이스에서 결제, 크레딧, 환불, 조정에 이르는 완전한 연결 고리
- 원시 항목에서 총합을 재계산하면 같은 답을 얻을 수 있는 능력
예시: 고객이 $100을 결제하고 $20 크레딧을 받았다면, 보고서는 원래 인보이스 금액을 수정하지 않고도 $100 수납, $20 크레딧, 순액 $80을 보여야 합니다.
인보이스, 결제, 크레딧, 조정은 분리하세요
정산 가능한 청구 원장 스키마를 원한다면, 각 문서 유형을 서로 다른 이벤트 종류로 취급하세요. 이들을 하나의 “transactions” 테이블에 섞어두면 깔끔해 보일 수 있지만 의미가 흐려집니다.
**인보이스(invoice)**는 청구(claim)입니다: “고객이 우리에게 돈을 빚지고 있다.” 인보이스는 헤더(고객, 인보이스 번호, 발행일, 만기일, 통화, 총합)와 별도의 라인 아이템(판매 항목, 수량, 단가, 세금 분류)으로 저장하세요. 속도를 위해 헤더 합계를 저장해도 되지만, 항상 라인에서 합계가 설명되어야 합니다.
**결제(payment)**는 자금 이동입니다: “현금이 고객에서 우리로 이동했다.” 카드 흐름에서는 인증(authorization)과 캡처(capture)를 볼 수 있습니다. 많은 시스템은 인증을 운영 기록으로 유지하고, 캡처된 결제만 원장에 넣어 현금 보고를 부풀리지 않습니다.
**크레딧 메모(credit memo)**는 고객의 미지급액을 줄이지만 반드시 현금을 반환하는 것은 아닙니다. **환불(refund)**은 현금이 나가는 것입니다. 이 둘은 함께 일어날 수 있으나 동일하지 않습니다.
- 인보이스: 매출채권과 매출(또는 이연수익) 증가
- 결제: 현금 증가 및 매출채권 감소
- 크레딧 메모: 매출채권 감소
- 환불: 현금 감소
**조정(adjustment)**은 현실이 기록과 맞지 않을 때 팀이 하는 수정입니다. 조정은 재무가 신뢰할 수 있도록 맥락이 필요합니다. 누가 만들었는지, 언제 게시했는지, 이유 코드, 짧은 메모를 저장하세요. 예: “반올림으로 0.03 탕감”, “레거시 잔액 마이그레이션”.
실무 규칙: “이 항목이 누군가 실수하지 않았다면 존재했을까?” 라고 물어보세요. 인보이스, 결제, 크레딧 메모, 환불은 존재합니다. 조정은 드물고 명확하게 라벨링되어 검토하기 쉬워야 합니다.
재무가 감사할 수 있는 원장 모델 선택하기
정산 가능한 청구 원장 스키마는 한 가지 아이디어에서 출발합니다: 문서는 무슨 일이 일어났는지를 설명하고, 원장 게시(postings)는 총합을 증명한다. 인보이스, 결제, 크레딧 메모는 문서입니다. 원장은 합산되는 항목들의 집합입니다.
문서 vs 게시(둘 다 저장하세요)
사람이 읽을 수 있도록 문서(인보이스 헤더와 라인, 결제 영수증, 크레딧 메모)를 보관하세요. 하지만 재무 조정의 진실로 문서 합계만 의존하지 마세요.
대신 각 문서를 원장 테이블에 하나 이상의 불변 항목으로 게시하세요. 그러면 재무는 계정별, 고객별, 통화별, 게시일별로 항목을 합산해 언제나 같은 답을 얻을 수 있습니다.
감사 친화적 모델의 몇 가지 규칙:
- 불변 항목: 게시된 금액을 편집하지 마세요; 변경은 새 항목으로 기록
- 명확한 게시 이벤트: 각 문서는 고유한 참조가 있는 게시 배치를 생성
- 대등성(밸런스) 로직: 항목들이 올바르게 합산(종종 회사 수준에서 차변=대변)
- 날짜 분리: 문서 날짜(고객에게 보여지는 날짜)와 게시 날짜(보고에 반영되는 날짜)를 구분
- 안정적인 참조: 외부 참조(인보이스 번호, 결제 프로세서 ID)를 내부 ID와 함께 저장
자연 키 vs 대리 키
조인과 성능을 위해 대리 키(surrogate IDs)를 사용하되, 마이그레이션이나 재수입 시에도 유지되는 안정적인 자연 키를 함께 저장하세요. 재무는 데이터베이스 ID가 바뀐 뒤에도 “Invoice INV-10483”을 요청할 수 있습니다. 인보이스 번호와 공급자 ID(결제 프로세서 청구 ID 등)를 일급 필드로 취급하세요.
히스토리 삭제 없이 역전하기
무언가를 되돌려야 할 때 삭제하거나 덮어쓰지 마세요. 역전 게시(reversal)를 하세요: 원래 금액을 반대로 하는 새 항목을 만들고 원래 게시에 링크하세요.
예: 잘못 적용된 $100 결제는 역전 게시로 잘못된 적용을 되돌리고, 올바른 인보이스에 새 적용을 게시하는 두 단계로 처리합니다.
단계별 스키마 청사진(테이블과 키)
각 문서 유형에 자체 테이블을 두고 명시적 할당(allocation) 레코드로 연결하면(나중에 관계를 추측하지 않음) 청구 원장은 더 신뢰성 있게 정산됩니다.
다음은 명확한 기본 키(UUID 또는 bigserial)와 필수 외래 키로 시작하는 핵심 테이블 집합입니다:
- customers:
customer_id(PK),external_ref같은 안정적 식별자(고유) - invoices:
invoice_id(PK),customer_id(FK),invoice_number(unique),issue_date,due_date,currency - invoice_lines:
invoice_line_id(PK),invoice_id(FK),line_type,description,qty,unit_price,tax_code,amount - payments:
payment_id(PK),customer_id(FK),payment_date,method,currency,gross_amount - credits:
credit_id(PK),customer_id(FK),credit_number(unique),credit_date,currency,amount
그다음 총합을 감사 가능하게 만드는 할당 테이블을 추가하세요. 하나의 결제나 크레딧이 여러 인보이스에 적용될 수 있고, 하나의 인보이스가 여러 결제로 지불될 수 있습니다.
조인 테이블은 자체 키를 가지세요(복합 키만 사용하지 마세요):
- payment_allocations:
payment_allocation_id(PK),payment_id(FK),invoice_id(FK),allocated_amount,posted_at - credit_allocations:
credit_allocation_id(PK),credit_id(FK),invoice_id(FK),allocated_amount,posted_at
마지막으로 조정을 별도로 두어 재무가 무엇이 왜 변경됐는지 볼 수 있게 하세요. adjustments 테이블은 대상 레코드를 invoice_id(nullable)로 참조하고, 히스토리를 덮어쓰지 않는 델타 금액을 저장할 수 있습니다.
돈이 게시되는 곳에는 항상 감사 필드를 추가하세요:
created_at,created_byreason_code(탕감, 반올림, 고객 호의, 차지백 등)source_system(manual, import, Stripe, support tool)
크레딧, 환불, 탕감과 깨지지 않는 총합
대부분의 정산 문제는 크레딧과 환불을 “음수 결제”로 기록하거나 탕감을 인보이스 라인에 섞어 넣을 때 시작합니다. 깔끔한 청구 원장 스키마는 각 문서 유형을 별도의 레코드로 유지하고, 이들이 상호작용하는 유일한 장소는 명시적 할당입니다.
크레딧은 고객의 미지급액을 왜 줄였는지 보여줘야 합니다. 하나의 인보이스에 적용된다면 단일 크레딧 메모를 생성하고 그 인보이스에 할당하세요. 여러 인보이스에 걸쳐 적용된다면 동일한 크레딧 메모를 여러 인보이스에 할당하세요. 크레딧은 여러 할당을 가진 하나의 문서로 남습니다.
환불은 결제와 유사한 이벤트이지 음수 결제가 아닙니다. 환불은 현금이 나가는 것이므로 별도의 레코드로 처리(보통 원래 결제와 참조로 연결)하고 결제처럼 할당하세요. 이렇게 하면 은행 명세서에 들어온 결제와 나간 환불이 모두 있을 때 감사 추적이 명확합니다.
부분 결제와 부분 크레딧도 동일하게 작동합니다: 결제나 크레딧 총액은 자체 행에 보관하고, 각 인보이스에 얼마가 적용되었는지는 할당 행으로 기록하세요.
이중 계산을 방지하는 게시 규칙
다음 규칙은 대부분의 “미스터리 차이”를 제거합니다:
- 음수 결제를 저장하지 마세요. 환불 레코드를 사용하세요.
- 인보이스를 게시한 후에는 총액을 줄이지 마세요. 크레딧 메모나 조정을 사용하세요.
- 문서는 한 번만 게시(
posted_at타임스탬프)하고 게시 후 금액을 편집하지 마세요. - 인보이스 잔액을 변경하는 유일한 방법은 게시된 할당의 합입니다.
- 탕감은 이유 코드가 있는 조정이며 인보이스에 크레딧처럼 할당됩니다.
세금, 수수료, 통화, 반올림 처리
대부분의 정산 문제는 재구성할 수 없는 총합에서 시작합니다. 가장 안전한 규칙은 단순합니다: 청구를 만든 원시 라인을 저장하고 고객에게 보여준 합계도 저장하세요.
세금과 수수료: 라인 수준에서 보관하세요
라인 아이템별로 세금과 수수료 금액을 저장하세요. 제품마다 세율이 다를 수 있고, 수수료가 과세 대상인지 아닌지, 면제가 일부 인보이스 항목에만 적용되는 경우가 많습니다. tax_total 하나만 저장하면 결국 설명할 수 없는 사례를 만나게 됩니다.
보관 항목:
- 원시 라인(판매 항목, 수량, 단가, 할인)
- 계산된 라인 합계(
line_subtotal,line_tax,line_total) - 인보이스 요약 합계(
subtotal,tax_total,total) - 사용된 세율과 세금 유형
- 수수료는 별도 라인으로 처리(예: “결제 처리 수수료”)
이렇게 하면 재무는 합계를 재구성하고 세금이 매번 같은 방식으로 계산되었는지 확인할 수 있습니다.
다중 통화: 실제 발생한 내용과 보고 방식 모두 저장
다중 통화를 지원하면 거래 통화와 보고 통화 값을 모두 기록하세요. 최소한으로는 모든 금액 문서에 currency_code, 게시 시 사용한 fx_rate, 그리고 장부 마감 통화로의 보고 금액(amount_reporting 같은)을 저장하세요.
예: 고객이 100.00 EUR와 20.00 EUR VAT로 청구되면, 해당 EUR 라인과 합계, 게시 시 사용된 fx_rate, 보고용 환산 합계를 저장하세요.
반올림은 별도 처리 대상입니다. 라인별 또는 인보이스별 반올림 규칙 중 하나를 선택해 일관되게 적용하세요. 반올림으로 차이가 발생하면 그 차이는 반올림 조정 라인(또는 소액 조정 항목)으로 명시적으로 기록하세요.
상태, 게시 날짜, 그리고 저장하지 말아야 할 것들
“상태”를 회계적 진실의 지름길로 사용할 때 정산이 혼란스러워집니다. 상태는 워크플로우 라벨로 취급하고, 게시된 원장 항목을 회계적 진실로 삼으세요.
상태는 엄격하고 단순하게 만드세요. 각 상태는 이 문서가 총합에 영향을 줄 수 있는지 여부를 답해야 합니다:
- Draft: 내부 전용, 게시되지 않음, 보고서에 반영되면 안 됨
- Issued: 확정되어 발송되었으며 게시 준비(또는 이미 게시됨)
- Void: 취소됨; 게시된 경우 역전되어야 함
- Paid: 게시된 결제와 크레딧으로 완납됨
- Refunded: 환불이 게시되어 현금이 나감
날짜는 대부분 팀이 예상하는 것보다 더 중요합니다. 재무는 “이 항목은 어느 달에 속하나요?”라고 묻고 UI 활동 로그에 의존하는 답변을 원하지 않습니다.
issued_at: 인보이스가 최종화된 시점posted_at: 회계 보고에 반영된 시점settled_at: 자금이 클리어되거나 결제가 확인된 시점voided_at/refunded_at: 역전이 효력을 발생한 시점
진실로 저장하지 말아야 할 것들: 원장에서 재구성할 수 없는 파생 수치입니다. balance_due, is_overdue, customer_lifetime_value 같은 필드는 캐시된 뷰로 괜찮지만, 항상 invoices, payments, credits, allocations, adjustments에서 재계산할 수 있어야 합니다.
작은 예: 결제 재시도가 게이트웨이에 두 번 전송되었습니다. 아이덴포텐시키(idempotency key)가 없으면 두 건의 결제가 저장되고 인보이스가 “paid”로 표시되어 재무가 추가된 $100을 보게 됩니다. 외부 청구 시도마다 고유한 idempotency_key를 저장하고 데이터베이스 차원에서 중복을 거부하세요.
재무가 처음부터 기대하는 리포트
청구 원장 스키마는 재무가 기본 질문에 빠르게 답하고 항상 같은 총합을 얻을 수 있을 때 스스로를 증명합니다.
대부분의 팀이 처음에 필요한 리포트는:
- 외상매출금(A/R) 연령 분석: 고객별로 남은 금액을 연령 구간(0-30, 31-60 등)으로 구분
- 수납 현금: 결제 게시일 기준으로 일/주/월별 수금액
- 수익 vs 현금: 인보이스 게시 vs 결제 게시
- 내보내기에 대한 감사 추적: GL 내보내기 라인에서 정확히 어떤 문서와 할당 행이 이를 만들었는지 드릴백할 수 있어야 함
연령 분석은 할당이 가장 중요한 곳입니다. 연령 분석은 “인보이스 합계 - 결제 합계”가 아닙니다. 특정 날짜 기준으로 각 인보이스에 무엇이 남아 있는가 입니다. 이를 위해 각 결제, 크레딧, 조정이 어떤 인보이스에 어떻게 적용되었는지와 그 할당이 언제 게시되었는지를 저장해야 합니다.
수납 현금은 인보이스 상태가 아니라 payments 테이블로 구동되어야 합니다. 고객은 일찍 지불하거나 늦게 지불하거나 부분 결제할 수 있습니다.
수익 vs 현금은 인보이스와 결제가 분리되어야 하는 이유입니다. 예: 3월 30일에 $1,000 인보이스 게시, 4월 5일에 $600 수납, 4월 20일에 $100 크레딧 발행. 수익은 3월(인보이스 게시), 현금은 4월(결제 게시)에 속하며, 크레딧은 게시된 시점에 매출채권을 줄입니다. 할당이 이 모든 것을 연결합니다.
예시 시나리오: 한 고객, 네 가지 문서 유형
한 고객, 한 달, 네 가지 문서 유형. 각 문서는 한 번만 저장되고 자금은 할당 테이블(때로는 “applications”라고도 함)을 통해 이동합니다. 이렇게 하면 최종 잔액을 재계산하고 감사하기 쉬워집니다.
가정: 고객 C-1001 (Acme Co.)
생성되는 레코드들
invoices
| invoice_id | customer_id | invoice_date | posted_at | currency | total |
|---|---|---|---|---|---|
| INV-10 | C-1001 | 2026-01-05 | 2026-01-05 | USD | 120.00 |
payments
| payment_id | customer_id | received_at | posted_at | method | amount |
|---|---|---|---|---|---|
| PAY-77 | C-1001 | 2026-01-10 | 2026-01-10 | card | 70.00 |
credits (크레딧 메모, 고객 호의 크레딧 등)
| credit_id | customer_id | credit_date | posted_at | reason | amount |
|---|---|---|---|---|---|
| CR-5 | C-1001 | 2026-01-12 | 2026-01-12 | service issue | 20.00 |
adjustments (사후 수정, 새로운 판매가 아님)
| adjustment_id | customer_id | adjustment_date | posted_at | note | amount |
|---|---|---|---|---|---|
| ADJ-3 | C-1001 | 2026-01-15 | 2026-01-15 | underbilled fee | 5.00 |
allocations (실제 잔액을 조정하는 항목)
| allocation_id | doc_type_from | doc_id_from | doc_type_to | doc_id_to | posted_at | amount |
|---|---|---|---|---|---|---|
| AL-900 | payment | PAY-77 | invoice | INV-10 | 2026-01-10 | 70.00 |
| AL-901 | credit | CR-5 | invoice | INV-10 | 2026-01-12 | 20.00 |
인보이스 잔액 계산 방법
INV-10에 대해 감사자는 소스 행에서 미결 잔액을 재계산할 수 있습니다:
open_balance = invoice.total + sum(adjustments) - sum(allocations)
따라서: 120.00 + 5.00 - (70.00 + 20.00) = 35.00가 미결금입니다.
“35.00”을 역추적하는 방법:
- 인보이스 합계(INV-10)에서 시작
- 같은 인보이스에 묶인 게시된 조정(ADJ-3)을 더함
- 해당 인보이스에 적용된 각 게시된 할당(AL-900, AL-901)을 뺌
- 각 할당이 실제 소스 문서(PAY-77, CR-5)를 가리키는지 확인
- 타임라인을 설명하기 위해 날짜와
posted_at을 검증
정산을 망치는 흔한 실수
대부분의 정산 문제는 “수학 버그”가 아닙니다. 규칙이 빠져 있어서 동일한 실제 사건이 누가 다루느냐에 따라 서로 다른 방식으로 기록됩니다.
흔한 함정은 음수 행을 단축키로 사용하는 것입니다. 음수 인보이스 라인, 음수 결제, 음수 세금 라인은 각기 다른 뜻일 수 있습니다. 음수를 허용한다면 하나의 명확한 역전 정책만 정의하세요(예: 원래 행을 참조하는 역전 행만 허용하고, 역전 의미와 할인 의미를 혼동하지 않음).
다른 빈번한 원인은 히스토리 변경입니다. 인보이스가 발행되었다면 나중에 가격이나 주소를 맞추기 위해 편집하지 마세요. 원본 문서를 유지하고 변경을 설명하는 조정이나 크레딧 문서를 게시하세요.
일반적으로 총합을 망치는 패턴:
- 참조 원래 행과의 명확한 링크가 없는 음수 행 사용
- 발행 후 인보이스를 편집하는 대신 조정이나 크레딧 노트를 게시하지 않음
- 게이트웨이 거래 ID와 내부 ID를 매핑 테이블 없이 섞어 소스 오브 진을 불명확하게 함
- 애플리케이션 코드가 합계를 계산하게 두고 지원 행(세금, 수수료, 반올림, 할당)이 누락됨
- “돈이 이동한 것”(현금 이동)과 “돈이 어디에 할당되었는가”(어떤 인보이스에 적용되었는가)를 분리하지 않음
마지막 지점이 가장 큰 혼란을 일으킵니다. 예: 고객이 $100을 결제하고 $60을 인보이스 A에, $40을 인보이스 B에 적용했다면 결제는 하나의 현금 이동이지만 두 개의 할당을 생성합니다. 결제=인보이스만 저장하면 부분 결제, 초과지급, 재할당을 지원할 수 없습니다.
체크리스트와 다음 단계
추가 기능을 더하기 전에 기본이 유지되는지 확인하세요. 청구 원장 스키마는 모든 총합이 특정 행으로 추적 가능하고 모든 변경에 감사 기록이 있을 때 정산됩니다.
빠른 정산 검사
소규모 샘플(한 고객, 한 달)과 전체 데이터셋에 대해 다음 검사를 실행하세요:
- 보고서의 모든 게시 숫자가 소스 행(인보이스 라인, 결제, 크레딧 메모, 조정)로 역추적되고 게시일과 통화가 포함되어 있는가
- 할당 합계가 적용 대상 문서의 총액을 초과하지 않는가(결제 할당 합계는 결제 총액 이하; 크레딧도 동일)
- 아무 것도 삭제되지 않는가. 잘못된 항목은 이유를 달아 역전된 다음 새 게시 행으로 수정되는가
- 오픈 밸런스는 저장된 값이 아니라 유도 가능한 값인가(인보이스 오픈 금액 = 인보이스 총액 - 게시된 할당 및 크레딧)
- 문서 합계가 라인과 일치하는가(인보이스 헤더 합계 = 라인 합계 + 세금 + 수수료, 선택한 반올림 규칙에 따라)
배포 가능한 시스템으로 가기 위한 다음 단계
스키마가 탄탄해지면 그 주위에 운영 워크플로우를 구축하세요:
- 인보이스, 결제, 크레딧, 조정을 생성·게시·역전하는 관리자 화면과 필수 메모
- 문서와 할당을 나란히 보여주는 조정 뷰(누가 언제 게시했는지 포함)
- 재무가 기대하는 내보내기(게시일 기준, 고객별, GL 매핑별)
- 기간 마감 워크플로우: 마감된 월의 게시 날짜 잠금, 늦은 수정을 위한 역전 항목 요구
- 테스트 시나리오(환불, 부분 결제, 탕감)로 예상 총합과 일치하는지 확인
빠르게 내부 재무 포털을 만들고 싶다면 AppMaster (appmaster.io)는 PostgreSQL 스키마 모델링, API 생성, 관리자 화면 생성을 동일한 소스에서 도와 게시 및 할당 규칙을 앱 진화에 맞춰 일관되게 유지하게 해줍니다.
자주 묻는 질문
조정(reconciliation)은 보고된 총합을 소스 레코드에서 재계산하고 날짜가 있는 항목으로 역추적할 수 있음을 뜻합니다. 보고서에 $12,430이 수집되었다고 나오면, 그 숫자에 합계가 맞는 게시된 결제와 환불을 정확히 가리킬 수 있어야 하며, 덮어쓴 필드에 의존하면 안 됩니다.
가장 흔한 원인은 paid_amount나 balance_due 같은 변경되는 “결과”를 사실처럼 저장하는 것입니다. 재시도, 버그, 수동 편집으로 그 필드들이 변경되면 과거 기록이 사라지고 총합이 실제 상황과 맞지 않게 됩니다.
각 항목은 서로 다른 실제 사건과 회계적 의미를 갖습니다. 인보이스, 결제, 크레딧, 환불을 단일 “transactions” 레코드에 선택적 필드로 억지로 넣으면 보고는 추측이 되고 감사는 그 행의 의미를 놓고 논쟁이 됩니다.
크레딧 메모는 고객의 미지급액을 줄이는 문서지만 현금을 보내는 것은 아닙니다. 환불은 실제로 현금이 나가는 이벤트로, 보통 이전 결제와 연관됩니다. 둘을 동일시하면 현금 보고와 은행 대조가 매우 어려워집니다.
편집이나 삭제 대신 역전(reversal)을 게시하세요. 원래 값과 반대 부호의 새 항목을 만들어 원래 게시와 연결하고, 그 다음에 수정된 할당을 게시하면 감사 추적에서 무엇이 언제 왜 변경되었는지 명확히 보입니다.
결제나 크레딧을 하나 이상의 인보이스에 연결하는 명시적 할당(application) 레코드를 사용하세요. 할당에는 할당된 금액과 게시일이 있어야 합니다. 인보이스의 미결잔액은 인보이스 합계와 조정 합계에서 게시된 할당을 뺀 값으로 계산되어야 합니다.
문서 날짜와 게시 날짜를 모두 보관하세요. 문서 날짜는 고객이 보는 날짜이고, 게시 날짜는 재무 보고와 기간 마감에 반영되는 날짜입니다. 이를 구분하면 월말 보고가 UI 활동 로그에 따라 달라지지 않습니다.
세금과 수수료는 라인 수준에 저장하세요. 다양한 상품은 서로 다른 세율을 가질 수 있고, 면제가 일부 항목에만 적용될 수 있습니다. tax_total만 보관하면 다양한 사례에서 설명할 수 없는 상황이 생깁니다.
거래 통화의 금액과 게시 시 사용된 환율을 모두 보관하세요. 게시 시 사용한 FX 비율과 보고용 환산 금액을 저장하면 장부가 단일 통화로 마감될 때도 소급 재구성이 가능합니다. 반올림 규칙은 라인 또는 인보이스 단위 중 하나를 선택하고 일관되게 적용하세요. 반올림 차이는 별도의 반올림 조정 항목으로 명시적으로 기록하세요.
상태는 워크플로우 라벨로 사용하고, 회계의 진실은 게시된 원장 항목과 할당으로 삼으세요. 상태가 틀릴 수 있으므로 불변의 게시 항목으로 재무가 동일한 방식으로 총합을 재계산할 수 있어야 합니다.


