반려된 문서를 고쳤다면 이전 신청을 다시 살려도 될까. 나는 현재 내용이 달라졌다는 사실과 과거에 반려됐다는 사실을 함께 남길 수 있어야 한다고 판단했다.

이번 제작기는 승인 회차를 따로 보관한 새 합성 실험을 다룬다. 실제 업체의 반려 사유나 결재자는 사용하지 않고, 같은 주문이 두 번째 검토로 넘어가는 경계를 확인했다.

반려는 문서를 없애는 행동이 아니다

반려된 문서가 사라지면 무엇을 고쳐야 하는지 다음 사람이 다시 찾아야 한다. 반려 표시를 지우기만 해도 처음 판단의 이유는 없어진다. 이번 모델에서는 문서를 rejected 상태로 두고 승인 회차에는 반려한 사람과 사유를 남겼다. 문서가 지금 어디에 있는지와 과거 검토에서 무슨 결정을 했는지 다른 곳에 보관한다.

사유는 짧은 합성 문자열만 사용한다. 실제 업무에서는 수정할 필드와 필요한 설명을 연결하면 좋지만 민감한 고객 정보가 무제한으로 들어가지 않도록 해야 한다. 단순히 반려라는 색상을 보여 주는 것보다 담당자가 다음에 고칠 것을 찾는 설명이 필요하다. 이 패키지는 그 설명을 보관할 자리를 만들었고 실제 회사의 사유 작성 정책은 별도다.

두 번째 신청은 첫 회차를 고쳐 쓰지 않는다

첫 내용 버전 0을 제출하면 회차 1이 생긴다. 다른 담당자가 반려한 뒤 수량을 8개로 고치면 내용 버전은 1이 되고 문서는 초안으로 돌아간다. 다시 제출할 때 회차 2가 만들어지고 새로운 target_revision은 1을 가리킨다. 과거 회차의 반려 상태는 그대로 남으므로 어떤 내용을 다시 확인하는지 읽을 수 있다.

이 예제에서는 내용을 고치지 않은 반려 문서도 사유와 함께 재제출할 수 있다. 반려가 반드시 수량 변경을 요구하는 것은 아니기 때문이다. 내용이 같아도 회차는 새로 생긴다. 실무에서 설명 보완만 허용할지 반드시 특정 값을 고치게 할지는 별도 정책이다. 문서의 내용 버전과 검토를 요청한 회차를 같은 번호로 묶지 않은 이유가 여기에 있다.

제출해 둔 내용을 고치면 대기 승인은 만료된다

문서가 pending인 동안 수정하는 경우도 생각해야 한다. 처음 요청한 내용으로 승인 대기함에 남겨 놓고 수량만 바꾸면 담당자는 다른 내용을 확인할 수 있다. 이번 수정 명령은 남아 있는 pending 회차를 superseded로 바꾸고 문서를 초안으로 돌린다. 이전 요청이 더 이상 현재 승인의 대상이 아니라는 뜻이다.

이를 승인 취소와 동일한 업무라고 일반화하지 않는다. 신청자가 회수하는 동작과 관리자가 취소하는 동작에는 다른 권한과 사유가 필요할 수 있다. 현재 코드는 내용 변경 때 대기 대상을 무효화하는 경계만 구현한다. 과거 회차를 지워 깨끗한 목록으로 만드는 대신 왜 새 검토가 필요한지 설명할 수 있는 기록을 택했다.

회차를 만드는 순간도 동시 수정의 대상이다

두 사람이 같은 반려 문서를 동시에 재제출하면 회차가 둘 생길 수 있을까. 이번 코드는 쓰기 트랜잭션 안에서 현재 문서 버전과 마지막 회차를 읽는다. 성공한 제출이 문서 버전을 올리면 같은 예상 번호의 다음 제출은 충돌한다. MAX에 가까운 회차 계산만 함수 밖에서 하고 나중에 저장하지 않은 이유다.

이 조건은 로컬 SQLite 실험의 저장 경계에서 읽어야 한다. 다른 저장소에 옮길 때는 그곳의 조건부 쓰기와 고유 제약을 함께 확인해야 한다. 화면에서 두 번째 제출 버튼을 잠깐 막는 것만으로 데이터베이스의 중복 회차가 해결되지는 않는다. 같은 요청 재응답과 다른 요청의 버전 충돌도 구별해서 다룬다.

새 담당자도 과거 결정을 이어 읽을 수 있게

기록 뷰어의 반려와 재제출 사례에는 현재 문서와 사건 수가 함께 있다. 독립 테스트는 첫 회차가 rejected, 두 번째가 pending이고 승인 대상 내용 버전이 달라지는지 대조한다. 현재 상태만 보고 과거 반려가 없었던 것처럼 설명하지 않는 것이 확인 기준이다. 이 기록은 모두 임시 합성 DB에서 만들어졌다.

실제 적용에서는 반려 사유의 공개 범위, 신청 회수, 대리 승인, 알림과 보관 기간을 추가로 정해야 한다. 이 글에서 실제 외부 알림이나 운영 결재를 수행한 것은 아니다. 나는 재제출을 처음부터 다시 쓰는 불편으로만 보지 않고 어떤 변경을 다시 확인받는 과정인지 드러내는 방법으로 설계했다. 코드와 테스트에서 그 출발점을 공유한다.