발행일

신청 항목 셋 중 한 건이면 제출 — 조건이 단계를 가로지르면 안내도 단계 밖에 둔다

신청 항목 셋 중 한 건이면 제출

표준데이터 등록 신청서는 다섯 단계입니다. 기본정보 → 데이터 엘리먼트 → 데이터 프레임 → 메시지 → 신청 사유. 가운데 세 단계가 실제 항목을 쓰는 자리예요.

제출 조건은 메시지 한 건이었습니다. 엘리먼트와 프레임은 0건이어도 되고, 메시지만 정확히 한 건 있으면 제출됐어요. 이 조건이 9월 9일에 풀렸습니다. 엘리먼트·프레임·메시지를 통틀어 최소 한 건이면 제출됩니다. 엘리먼트 하나만 등록하려는 신청도 있으니까요.

바꾸는 일 자체는 조건문 한 줄입니다. 그 조건문이 몇 군데서 말해지고 있었는지가 문제였어요.

판정 조건을 셋 중 하나로

같은 날 오후, 팀원이 제출 확인창과 서버 POST가 판정 함수 submission_errors() 하나를 쓰도록 묶어 둔 상태였습니다. 그 안의 조건은 "메시지가 있나"였고, 저는 그 조건을 has_any_item()으로 떼어 셋 중 하나로 바꿨어요.

def has_any_item(application):
    """엘리먼트·프레임·메시지 중 한 건이라도 작성됐는지."""
    document = normalized_document(application.document)
    return bool(document["elements"] or document["frames"] or document["message"])

팀원이 9월 초에 만들어 둔 normalized_document()가 앞에 있는 게 중요합니다. 신청 항목은 JSON 스냅샷 한 덩어리로 저장되는데, 메시지 자리에 리스트나 빈 사전이 들어와 있을 수 있어요. 이 함수는 메시지가 사전일 때만 메시지로 인정합니다.

def normalized_document(document):
    source = document if isinstance(document, dict) else {}
    return {
        "elements": list(source.get("elements") or []),
        "frames": list(source.get("frames") or []),
        "message": (
            source.get("message") if isinstance(source.get("message"), dict) else None
        ),
    }

이게 없으면 메시지 자리에 이름만 든 리스트 같은 값이 있어도 참으로 세어져 제출이 통과합니다. 판정 입구가 이미 하나라 이 방어도 그대로 따라왔어요.

단계 정의에서는 메시지 단계의 optionalTrue로 바꿨습니다. 이제 세 단계가 전부 선택이에요. 제출 조건은 단계별 필수가 아니라 셋을 가로지르는 조건이라, 작성 화면의 (선택) 표시와 [다음 단계] 활성 조건이 셋 다 같아집니다.

보완 링크도 4단계에서 2단계로 옮겼어요. 뭘 써야 할지 모르는 사람을 가장 작은 단위로 보내는 게 맞습니다.

안내를 단계 밖으로 꺼냈다

그날 오후 팀원이 붙여 둔 안내는 메시지 단계 본문 안에 있었습니다.

  {% if step.code == "MSG" and not items %}
    <p class="...">
      메시지는 필수 항목입니다. [+ 항목 추가]로 메시지 한 건을 작성하고 저장해야 제출할 수 있습니다.
    </p>
  {% endif %}

작성 화면의 2~4단계 본문은 서버가 그린 조각이고, 저장·수정·삭제 때마다 그 단계 조각만 통째로 갈아 끼워집니다. 그래서 조건이 한 단계 안에 갇혀 있을 때는 이 자리가 맞았어요. 메시지를 저장하면 메시지 조각이 다시 그려지면서 안내가 사라집니다.

조건이 세 단계를 가로지르는 순간 이 배치가 깨집니다. 2단계에서 엘리먼트를 하나 넣으면 제출 조건은 충족되는데, 다시 그려지는 건 2단계 조각뿐이에요. 4단계에 붙어 있는 "메시지는 필수" 안내는 낡은 채로 그대로 남습니다.

그래서 안내를 세 단계 위 한 줄로 옮겼습니다.

      {# 제출 조건은 단계별 필수가 아니라 세 단계를 가로지른다 — 한 단계 안에 붙이면
         다른 단계에서 항목을 넣어도 그 조각만 다시 그려져 안내가 낡은 채로 남는다 #}
      <p class="rounded-lg border border-primary bg-primary/10 px-4 py-3 text-sm text-gray-700">
        데이터 엘리먼트·데이터 프레임·메시지 중 최소 한 건은 작성해야 제출할 수 있습니다.
      </p>

안내가 사는 자리는 조건이 걸치는 범위를 따라갑니다. 부분 갱신이 있는 화면에서는 이게 취향이 아니라 정확성 문제예요.

안내창은 사유와 방법을 갈랐다

제출 버튼을 누르면 확인창이 뜨고, 조건을 못 채웠으면 보완 사유가 나옵니다. 팀원이 오후에 붙인 사유는 한 문장이었어요.

메시지는 필수 항목입니다. 4. 메시지 단계에서 [+ 항목 추가]로 메시지 한 건을 작성하고 저장해 주세요.

이걸 무엇이 잘못됐나어디서 무엇을 하나로 나눴습니다. 앞은 굵은 경고색, 뒤는 회색 설명이에요. 한 덩어리로 붙여 놓으면 긴 문장 하나로 읽혀서 어느 단계로 가야 할지가 안 보입니다.

        errors.append(
            {
                "step": 2,
                "message": _("데이터 엘리먼트·데이터 프레임·메시지 중 최소 한 건은 작성해야 합니다."),
                "guide": _("2~4단계에서"),
                "guide_button": _("+ 항목 추가"),
                "guide_tail": _("로 한 건을 작성하고 저장해 주세요."),
            }
        )

버튼 이름을 따로 뗀 이유는 화면에서 실제 버튼 모양으로 그리기 위해서입니다. 대괄호로만 감싸면 문장에 묻혀요. 창 폭도 24rem에서 28rem으로 넓혔는데, 보완 사유가 붙으면 좁은 폭에서 문장이 너덜너덜 접히기 때문입니다.

서버가 POST를 막을 때는 반대로 보완 방법을 안 붙입니다.

                # 보완 방법(guide)은 붙이지 않는다 — 곧바로 그 단계로 보내고,
                # 거기 상단 안내가 무엇을 해야 하는지 이미 말한다. 여기까지 이으면
                # 경고 한 줄이 세 문장짜리 붉은 덩어리가 된다.

어차피 그 단계로 보내고, 도착한 화면 위에 방금 옮겨 놓은 안내 한 줄이 서 있으니까요. 같은 말을 두 번 하지 않습니다.

75분 뒤, 이용안내가 아직 거짓말을 하고 있었다

같은 날 밤 이용안내를 열어 봤더니 표준데이터 탭 4단계 설명이 이랬습니다.

  1. 메시지: 교환되는 최상위 단위입니다. … 신청서 한 건에 메시지 하나이며 이 단계는 필수입니다.

75분 전에 제가 푼 조건이었어요. 같은 커밋에서 이용안내의 1단계 용어(식별 이름표준데이터 이름)까지 고쳐 놓고, 세 줄 아래 4단계 문장을 놓쳤습니다. 규칙 문장은 화면 단위가 아니라 문장 단위로 찾아야 했어요. 그 자리도 결국 하나의 화면이고, 사용자는 오히려 여기를 먼저 읽어요.

  1. 메시지: … 신청서 한 건에 메시지 하나이며, 2~4단계를 통틀어 최소 한 건을 작성해야 제출할 수 있습니다.

이 규칙을 말하는 자리가 네 곳이라는 걸 이때 셌습니다. 서버 판정(has_any_item), 작성 화면 상단 안내, 제출 확인창, 이용안내 본문. 판정은 한 곳에 있지만 문장은 여전히 셋이고, 그 셋은 코드가 맞춰 주지 않습니다. 화면이 없어진 기능을 설명하고 있던 8월 일과 같은 계보예요. 그때는 몇 달 묵은 거짓이었고 이번엔 75분짜리였다는 차이뿐입니다.

용어도 같이 바꿨다

신청 1단계의 식별 이름표준데이터 이름으로 고쳤습니다. 신청·심사 화면, 확인 화면 항목 표, 심사 목록 검색창 안내문, 이용안내, 작성 예시 PDF까지요.

모델 필드명과 verbose_name은 그대로 뒀습니다. 마이그레이션이 필요해서 별도로 다뤄야 하거든요. 화면 라벨과 저장 필드명이 갈린 상태가 남았고, 이건 문서 정본에 적어 뒀습니다.

검증

새로 쓴 테스트는 둘입니다.

  • test_element_alone_opens_submit_dialog — 엘리먼트만 있을 때와 프레임만 있을 때를 subTest로 각각 돌려 제출창이 열리는지 봅니다. 이번 변경으로 새로 통과해야 하는 경로예요.
  • test_authoring_page_explains_one_of_three_requirement — 작성 화면 상단에 "셋 중 최소 한 건" 안내가 서는지.

나머지는 팀원이 같은 날 오후에 걸어 둔 잠금을 새 조건에 맞춰 고친 것입니다.

  • test_empty_document_explains_block_in_submit_dialog — 셋 다 비면 막히는지를 보던 확인창 테스트에, 보완 링크가 ?step=2로 가는지와 버튼 이름·앞뒤 문장이 다 나오는지를 기대값으로 바꿔 넣었습니다.
  • test_direct_submit_without_any_item_shows_error_and_preserves_draft — 화면을 오래 열어 뒀거나 직접 POST한 경우입니다. 메시지 자리에 비정상 값 네 가지를 넣어 돌리고, 기록 상태·작성 내용·심사 이력이 남고 제출 알림이 안 나가는지 단언하는 설계는 팀원 것 그대로예요. 엘리먼트가 있으면 이제 통과하니 문서의 엘리먼트를 비우고, 초안 보존을 확인할 값을 따로 넣었습니다.
  • 팀원이 만든 브라우저 검증 스크립트도 2단계 기준으로 기대값을 고쳤습니다.

남은 것 · 한계

  • 규칙을 말하는 문장이 여전히 세 곳입니다. 판정은 함수 하나에 있지만 안내 문구는 작성 화면·확인창·이용안내에 따로 적혀 있어요. 이번에 이용안내가 75분 늦은 것처럼, 다음에 조건이 또 바뀌면 같은 일이 납니다. 문구까지 한 곳에서 만들어 세 화면이 받아 쓰게 하는 게 맞지만 이번엔 안 했습니다.
  • 모델 필드명은 아직 identifier_name입니다. 화면은 "표준데이터 이름", DB는 "식별 이름"이에요. 마이그레이션을 미룬 대가로 둘이 갈린 상태를 안고 갑니다.
  • 저장은 항목이 없어도 됩니다. 초안 보존이 목적이라 그대로 뒀는데, 저장과 제출을 혼동하는 사람에게는 "저장했는데 왜 제출이 안 되나"가 여전히 남아요. 기존 '저장 후 확인' 안내에 기대고 있습니다.
  • 메시지는 여전히 한 건까지입니다. 필수만 풀었지 개수 제한은 그대로예요. 신청서 한 건에 메시지 하나라는 구조 자체는 이번 범위가 아니었습니다.

관련 글: 화면이 없어진 기능을 설명하고 있었다 · 제출 단계 칩에 스튜어드가 찍혀 있었다 · 신청서 단계 구획이 로딩 중에 전부 보였다 · 신청서 항목 모달에서 후보 조회 20회를 2회로 줄였다