kintex/_workspace/03_backend_wiring.md
zio a963c69141 feat(impl): M2~M5 배선(501→200) + 프론트 화면 4종
backend: M2 배치/규정검증(PostGIS)·M3 설계·M4 배선/견적·M5 RenderJob + 인증(login/workspaces/accept-invite) 매퍼 배선으로 501 해소. M2 자동배치 3안 생성→선택/병합→규정 재검증. 계약 확장(Violation 좌표·LayoutDto.hall·AutoLayoutOption.booths, additive NON_NULL). 신규 HallMapper·UtilityOrderMapper(기존 테이블). gradlew build 통과.

frontend: SCR-02 대시보드·SCR-06 설계스튜디오(4샷+Before/After)·SCR-07 배선뷰(적/청/녹)·SCR-12 갤러리 + CompareSlider(reroomai)·WiringCanvas. tsc/vite build EXIT 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 18:37:26 +09:00

86 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 킨텍스 백엔드 — M2~M5 매퍼 배선 + 계약 확장 (Phase C, D-4)
> 작성: kintex-backend-dev · 근거: `_workspace/01_backend_contracts.md`·`_workspace/02_db_schema.md`·`_workspace/02_frontend_progress.md`(계약 공백 5건)·IMPLEMENTATION_BACKLOG C-M2~C-M5
> 대상: **frontend·qa 대조**. 이번 회차는 db-engineer가 완성한 MyBatis 매퍼에 ServiceImpl을 배선해 **M2~M5·인증의 501(NOT_IMPLEMENTED)을 200으로 전환**하고, 프론트가 지적한 계약 공백을 **additive**(하위호환)로 보강한다. 스키마 마이그레이션·2FA·프론트·build.gradle 미변경. 커밋 없음(파일만).
---
## 1. 501 → 200 전환된 엔드포인트
| 모듈 | 메서드 | 엔드포인트 | 배선 요지 |
|---|---|---|---|
| 인증 | login | `POST /api/auth/login` | `UserMapper.findAuthByEmail` + BCrypt 대조 + `findEventRoles`→토큰·워크스페이스 |
| 인증 | listWorkspaces | `GET /api/auth/workspaces` | `findEventRoles(userId)`→WorkspaceDto[] |
| 인증 | acceptInvite | `POST /api/auth/accept-invite` | 등록업체 검증(`findCompanyRegistered`)→토큰 재발급. ★초대코드 매핑은 후속(아래 TODO) |
| M2 | getLayout | `GET …/halls/{hallId}/layout` | `findLayout`+`findBooths`(GeoJSON 파싱)+`HallMapper`+검증 요약 |
| M2 | saveLayout | `PUT …/layout` | 폴리곤→WKT `upsertLayout`+`replaceBooths`, 저장 후 규정 재검증 반영 |
| M2 | validate | `POST …/layout/validate` | `minAisleWidth`·`countExitsBlocked`+부스 최댓값→룰엔진 M2 |
| M2 | autoGenerate | `POST …/layout/auto-generate` | 제약 기반 그리드 패킹으로 **정확히 optionCount(기본 3)안** + S7 발행 |
| M2 | applyOption | `POST …/layout/apply-option` **(신규)** | 선택/병합 부스열→새 버전 저장+규정 재검증 |
| M3 | getDesign | `GET …/booths/{boothId}/design` | `findDesign`+spec(jsonb) 역직렬화 |
| M3 | saveDesign | `PUT …/design` | spec 직렬화 `upsertDesign`(버전 next), 최신본 반환 |
| M4 | computeWiring | `POST …/utility/wiring` | `isAssumedTrench`+단말별 `shortestPath`(LineString 파싱)·색상 규약 |
| M4 | submitOrder | `POST …/utility/order` | `quote`+`UtilityOrderMapper.insertOrder`(영속)+위치표시도 URL·마감/릴레이 고지 |
| M4 | getOrder | `GET …/utility` | `UtilityOrderMapper.findOrder`(부스 최신) |
| M5 | listByBooth | `GET …/render-jobs` | `RenderJobMapper.findByBooth`→갤러리 목록(워터마크 항상 포함) |
| M5 | publish(보강) | `POST …/render` | 쿼터 정본을 `countSucceededByEvent`(DB DONE)로 전환 + `insertJob` 내구 이력 |
| M5 | callback(보강) | `POST /api/internal/render/callback` | `updateStatus` 내구 갱신(쿼터는 DONE 집계에서 자동) |
---
## 2. 계약 확장 (additive · 하위호환 · 프론트 gap 해소)
모두 `@JsonInclude(NON_NULL)`**기존 필드 불변, 신규 nullable 필드만 추가**.
### 2-1. gap #1 — 위반 오버레이 좌표 (`rules.Violation`)
- 추가 필드: `boothId`(String, nullable) · `anchor`([x,y] 미터, nullable).
- **부스 단위 규칙(HEIGHT_MAX·FLOOR_LOAD)** 위반에 한해 해당 부스 id + 폴리곤 중심 앵커를 부여. 레이아웃 단위(AISLE_WIDTH_MIN·EXIT_ACCESS)는 null(캔버스에서 목록 핀만).
- 프론트: `violations[].anchor` 존재 시 캔버스 핀 위치로 사용, 없으면 기존처럼 목록 핀만.
```json
{ "pin":1, "code":"HEIGHT_MAX", "severity":"block", "measured":"측정 5.4m",
"boothId":"b-102", "anchor":[3.0,1.5] }
```
### 2-2. gap #2 — 홀 치수·트렌치 (`m2.dto.LayoutDto.hall` = `HallInfo`)
- `LayoutDto``hall` 필드 추가(신규 `HallInfo`). 캔버스 하드코딩(126×90m·트렌치 그리드) 제거 근거.
- `HallInfo`: `hallId·label·dimsM[w,d]·ceilingM·targetBoothCount·floorLoadTPerM2·assumedTrench·trenches[]·exits[]`.
- `HallInfo.Trench`: `{trenchId,x,y,assumed,supplies[power|water|air|network|gas]}` · `HallInfo.Exit`: `{x,y,clearanceM}`.
- 원천: 신규 `HallMapper`(hall·trench·hall_exit read-only). `assumedTrench=true`면 SCR-07 "가정 트렌치" 배지(PLANNING R4).
```json
"hall": { "hallId":"H7","label":"제2전시장 홀7","dimsM":[126,90],"ceilingM":12,
"targetBoothCount":510,"floorLoadTPerM2":5.0,"assumedTrench":true,
"trenches":[{"trenchId":"H7-T-3-3","x":3,"y":3,"assumed":true,"supplies":["power","water","air","network","gas"]}],
"exits":[{"x":63,"y":0,"clearanceM":3.0}] }
```
### 2-3. gap #3 — 자동배치 안의 부스 + 적용 경로 (`AutoLayoutOption.booths` + apply-option)
- `AutoLayoutOption``booths`(BoothDto[]) 추가 — "이 안으로 편집 시작"/병합 소스.
- 신규 `POST …/layout/apply-option` (ORGANIZER): body `ApplyOptionRequest {name, booths[]}` → 새 버전 저장 + 규정 재검증된 `LayoutDto`.
- **3안 흐름 마감**: auto-generate(3안+각 booths) → 클라 선택/구역 병합(booths 합성) → apply-option(영속+재검증). 단일 안 적용도 동일 엔드포인트.
---
## 3. backend 추가 매퍼 (db-engineer 계약 §3 밖 — 기존 테이블 read/write, 마이그레이션 불변)
> db-engineer 인수·정본화 요청. 모두 V2~V5 기존 테이블만 사용.
| 매퍼 | 메서드 | 테이블 |
|---|---|---|
| `m2.mapper.HallMapper` | findHall·findTrenches·findExits | hall·trench·hall_exit (read-only) |
| `m4.mapper.UtilityOrderMapper` | insertOrder·findOrder | utility_order (V3) |
| `auth.mapper.UserMapper`(+1) | findCompanyRegistered | company (read-only) |
---
## 4. 남은 TODO (경계·후속)
- **초대코드→행사/부스/역할 매핑**: invite 테이블·매퍼 미제공 → acceptInvite는 등록업체 게이트 + 현재 역할 재발급까지만. 역할 upsert는 매핑 매퍼 인수 시 배선(db-engineer).
- **위치표시도 PDF(M4-3)**: submitOrder는 신청 영속 + 자산 URL(`…/utility/location-diagram`)·고지 반환. 실제 PDF 렌더는 문서 큐(후속). 현재 해당 GET 자산은 미구현(URL만).
- **wiring 지오메트리 영속**: computeWiring은 산출·반환만. utility_order.wiring(MultiLineString) 저장은 후속.
- **자동배치 최적화**: 현재 결정적 그리드 패킹(3안=통로폭/규격 변주, 무위반 골격). 제약 솔버 고도화는 후속(흐름은 성립).
- **트렌치 실측(R4)**: 가정 그리드 유지(is_assumed=true). CAD 추출 교체 시 HallInfo.assumedTrench=false 자동 반영.
## 5. 검증
- `./gradlew compileJava` · `./gradlew build -x test` 통과. 매퍼 id/namespace 정합 확인. PostgreSQL 미기동으로 런타임 왕복은 dev 배포 후.
- 보안 불변 유지: password_hash/otp 응답 제외, 스택트레이스 미노출(요약 메시지), AI 이미지 워터마크·고지 항상 포함, 워커 에러 요약화.