파편화된 조직 데이터 위에 전사 ERP 결재선 구축하기

2026/07/13사내 시스템 · ERP 구축Next.js · TypeScript · PostgreSQL · Supabase

중고차 유통 비즈니스 전반을 아우르는 사내 엔터프라이즈 ERP 시스템입니다. 차량 매입, 상품화, 영업, 탁송, 재무, 인사, 전자결재까지 200개 이상의 화면으로 구성되어 있습니다.

단순히 화면 수가 많은 것이 문제가 아니었습니다. 가장 큰 난제는 조직 데이터의 잦은 개편으로 인해 기존 관계형 데이터가 끊임없이 파편화되어 있었다는 점이었습니다.

문제: 조직 개편마다 깨지는 과거 결재선

회사의 조직 개편이 있을 때마다 부서 마스터 테이블이 새로운 세대(Generation)로 재생성되고 있었습니다. 반면 사용자와 부서 간의 매핑 데이터는 구 세대 부서 ID를 참조한 채 방치되어 있었습니다.

이 구조에서 결재선을 부서 외래키(FK) 참조 방식으로 저장하면, 다음 조직 개편이 일어나는 순간 과거의 모든 결재 문서에서 '누가 어떤 권한으로 승인했는지'가 통째로 깨지는 치명적인 결함이 발생합니다.

해결: 불변 스냅샷과 세대 초월 식별자

  1. 세대 불변 식별자 도입: 부서 엔티티가 새로 생성되더라도 원래의 뿌리 부서가 어디인지 추적할 수 있는 계보 식별자를 도입해 조직 변경 전후를 연결했습니다.
  2. 결재선 스냅샷 저장: 결재 문서는 특정 '시점의 법적/업무적 기록'입니다. "홍길동 팀장이 2025년 3월에 영업1팀장 자격으로 승인했다"는 사실은 훗날 영업1팀이 해체되거나 직급 체계가 바뀌어도 결코 변해서는 안 됩니다. 따라서 결재선에는 외래키 참조 대신 결재 시점의 부서명, 직책, 권한 정보를 값(Value)으로 복사한 불변 스냅샷을 저장했습니다.

기존 스키마를 단 1줄도 건드리지 않은 무위험 확장

새로운 전자결재 모듈을 탑재하면서 기존 레거시 테이블 스키마는 단 한 줄도 수정하지 않았습니다. 결재 관련 독립 테이블 12개를 새로 설계하여 옆에 붙였습니다.

운영 중인 메인 테이블 스키마를 변경하는 것은 항상 런타임 리스크를 동반합니다. 격리된 새 테이블로 기능을 확장하면 만에 하나 문제가 생기더라도 기존 ERP 업무에 영향을 주지 않고 안전하게 롤백할 수 있습니다.

DB 트리거 기반의 강력한 불변성 강제

결재가 완료된 문서는 어떤 경우에도 사후 위변조될 수 없어야 합니다.

애플리케이션 계정의 권한 우회 가능성까지 원천 차단하기 위해, 문서 최종 승인 완료 시 DB 트리거를 통해 해당 레코드의 UPDATE/DELETE를 물리적으로 차단했습니다. 애플리케이션 레벨의 가드에만 의존하지 않고 데이터베이스 계층에서 최종 무결성을 보장한 것입니다.

트랜잭션 원자적 알림 발송 큐

결재 상신 시 수신자에게 슬랙이나 알림톡이 발송됩니다. 그러나 결재 저장 도중 트랜잭션이 롤백되었는데 알림만 먼저 전송되면 심각한 업무 혼선이 발생합니다.

이를 해결하기 위해 결재 저장과 알림 요청 레코드 저장을 동일한 DB 트랜잭션 안에서 원자적(Atomically)으로 처리했습니다. 별도의 백그라운드 워커가 이 알림 큐를 폴링하여 발송하므로, 결재가 롤백되면 알림 요청도 함께 사라져 헛알림이 절대 나가지 않습니다.

요약

ERP와 같은 핵심 사내 시스템에서는 화려한 기능보다 데이터의 영속성과 비즈니스 무결성을 지키는 방어적 아키텍처가 최우선입니다.