# W6 — 분쟁 대기 대상을 헤더로 (waiting_on)

작성 2026-08-19 · repo `cryptoments`(core, admin-api, partner-api, open-api) + `cryptoments-admin`(admin-ui, partner-ui)
**DDL 적용 완료** — `p2p_disputes.waiting_on VARCHAR(20) NULL` (운영 47행 전부 NULL, 정본 DDL 파일 반영됨)

---

## 배경 — 원칙 정리

분쟁 데이터를 세 곳이 나눠 갖고 있어 어긋난다. 원칙을 이렇게 잡았다.

```
dispute_events   무슨 일이 있었나        append-only 정본
p2p_disputes     지금 어떤 상태인가      status · waiting_on · due_at · result
카운터           들지 않는다             이벤트에서 센다
```

이 기준으로 보면:

```
waiting_on  "지금 누구 차례" = 현재 상태. 이벤트에서 도출 불가 → 헤더가 맞다   ← 이번 작업
round       카운터. REQUEST_MORE 이벤트 수로 도출 가능 → 중복이다              ← 다음 단계(이번 범위 아님)
```

**`round` 는 이번에 건드리지 마라.** 정리 대상이라는 판단은 섰지만 별도 단계다.

---

## 현재 무슨 문제인가

관리자가 증빙을 재요청하면 `p2p_matches.dispute_waiting_on` 에만 대상이 기록된다.
그런데 **`REQUEST_MORE` 이벤트에는 대상이 안 담긴다** — `memo` 만 담긴다.

```
관리자가 DEPOSITOR 에게 재요청
  → p2p_matches.dispute_waiting_on = 'DEPOSITOR'      ← 매칭 컬럼에만
  → dispute_events REQUEST_MORE                        ← 대상 유실
  → p2p_disputes                                       ← 아무것도 없음
```

W3-A 에서 헤더를 정본으로 삼기로 했는데 **"누구 차례"만 헤더에 없다.**
T5(매칭 분쟁 12컬럼 제거)가 이것 때문에 막혀 있다.

---

# A. 백엔드

## A1. `REQUEST_MORE` 이벤트에 대상을 담는다

`DisputeEventService.logDisputeEvent(...)` 는 `memo` 만 받아
`toPayloadJson(memo)` 로 `{"memo": ...}` 를 만든다. 대상이 들어갈 자리가 없다.

**기존 시그니처를 깨지 마라** — 호출부가 10곳이다. **오버로드를 추가**하라.

```
기존   logDisputeEvent(matchId, eventType, source, actor, reason, amountKrw, evidenceUrl, memo)
추가   ... 위와 동일 + Map<String,Object> extraPayload
       → payload_json = {"memo": ..., <extra>}
```

`requestMoreEvidence` 만 새 오버로드를 쓴다. 나머지 9곳은 그대로.

> **왜 payload_json 인가**: 대상으로 **조회를 거를 일이 없다**(현재 상태는 헤더가 갖는다).
> 이벤트의 대상은 감사 이력용이라 구조화 컬럼이 필요 없다. `dispute_events` 에 DDL 을 하지 않는다.

## A2. 헤더에 `waiting_on` 을 쓴다

`P2pDisputeService.markEvidenceRequested(matchId, round, dueAt)` (W3-A 에서 만든 것)에
**대상 파라미터를 더한다.**

```
markEvidenceRequested(matchId, round, dueAt, waitingOn)
```

`P2pMatchManagementService.requestMoreEvidence` 가 이미 `tgt`(정규화된 대상)를 갖고 있다.
그걸 넘겨라.

⚠️ **대상 값역은 `DEPOSITOR` / `WITHDRAWER` 뿐이다** — `P2pDisputeRequestMoreRequest` 검증이
그 둘 외에는 409 를 던진다. 헤더에도 그 값만 들어가야 한다.

⚠️ **판정으로 종결되면 `waiting_on` 을 비워라.** 종결된 분쟁에 "누구 차례"가 남아 있으면
대기열이 잘못 읽는다. `P2pDisputeService.resolve` 에서 `null` 로 지운다.

## A3. 응답 노출

```
admin    P2pDisputeDetailResponse.Header · P2pDisputeQueueItemResponse
         → 헤더의 waiting_on 을 담는다
partner  P2pDisputeDetailResponse(축약) · 대기열
         → 담는다. 상대 정보가 아니라 "지금 누구 차례"라 노출해도 된다
widget   P2pWidgetMatchDetailResponse.disputeWaitingOn
         → 이미 있다. 값의 출처를 매칭 → 헤더로 바꿀지 판단하라 (아래 참조)
```

**대기열(`P2pDisputeSearchMapper`)이 지금 매칭에서 `disputeWaitingOn` 을 끌어온다.**
헤더 컬럼으로 바꿔라 — 그게 이 작업의 목적이다.

## A4. 위젯 값 출처 판단

`P2pWidgetController` 가 `waitingLeg` 를 그룹 스캔해 `disputeWaitingOn` 을 내린다(W4).
헤더로 바꾸면 병합 카드 처리가 달라질 수 있다.

**바꿀지 유지할지 판단하고 근거를 보고하라.** 무리하게 바꾸지 마라 —
매칭 컬럼은 아직 살아 있고(T5 전), 위젯은 레그 단위로 동작한다.

---

# B. 프론트

`waiting_on` 을 표시하는 곳이 **헤더 값을 읽도록** 맞춘다.

```
admin-ui   매칭 상세 분쟁 카드 · 분쟁 대기열
partner-ui 거래추적 타임라인 헤더 요약 · 분쟁 목록
```

- 한글 라벨은 이미 만든 공용 상수(`disputeLabels.ts`)의 당사자 맵을 쓴다
- **매칭 컬럼과 헤더 값이 다르면?** 지금은 재요청이 0건이라 둘 다 NULL 이다.
  앞으로는 헤더가 정본이므로 **헤더만 읽어라.** 두 값을 비교해 보여주지 마라

---

## 코딩 규칙

- Java 17 · Lombok(`@Data` 금지) · DTO 멤버 JavaDoc 필수
- **`logDisputeEvent` 기존 시그니처를 깨지 마라** — 오버로드로 추가
- **`round` 를 건드리지 마라** — 다음 단계다
- MyBatis `<script>` 내 `<`·`<=`·`<>` 금지 → `&lt;`/`!=`
- **DDL 금지** — `waiting_on` 은 이미 적용됐다. 다른 컬럼이 필요하면 만들지 말고 보고
- 이벤트 적재·알림이 자금 트랜잭션을 깨면 안 된다 — 기존 try/catch 보호 유지
- Vue 는 기존 스타일. 새 라이브러리 금지

## 완료 기준

```
1  ./gradlew :core :admin-api :partner-api :open-api :scheduler compileJava 통과
2  admin-ui · partner-ui 빌드 통과
3  증빙 재요청 시 헤더 waiting_on 에 DEPOSITOR|WITHDRAWER 가 기록된다
4  REQUEST_MORE 이벤트 payload 에 대상이 남는다 (memo 와 함께)
5  판정으로 종결되면 waiting_on 이 비워진다
6  대기열·상세가 매칭이 아니라 헤더의 waiting_on 을 읽는다
7  logDisputeEvent 기존 호출부 9곳이 무변경이다
8  round 관련 코드가 변경되지 않았다 (diff 로 증명)
```

## 보고 형식

- 작업별 수정 파일:라인 + 한 줄
- A1 오버로드 방식과 기존 호출부 무변경 증명
- A4 위젯 판단과 근거
- 종결 시 `waiting_on` 을 비우는 지점
- `round` 무변경 증명
- 지침이 실제 코드와 어긋난 지점 — **고치지 말고 먼저 보고**
- 빌드 결과

## 착수 전 필수 확인

```
core/.../p2p/DisputeEventService.java                (logDisputeEvent · toPayloadJson · 호출부 10곳)
core/.../p2p/P2pDisputeService.java                  (markEvidenceRequested · resolve)
common/.../entity/P2pDispute.java                    (waiting_on 필드 추가 필요)
admin-api/.../service/P2pMatchManagementService.java (requestMoreEvidence · tgt 정규화)
admin-api/.../mapper/P2pDisputeSearchMapper.java     (지금 매칭에서 끌어오는 지점)
admin-api/.../dto/response/P2pDisputeDetailResponse.java
partner-api/.../dto/p2p/P2pDisputeDetailResponse.java
open-api/.../controller/widget/P2pWidgetController.java  (waitingLeg 그룹 스캔 — A4)
```

지침과 다르면 **멈추고 보고하라.**
