Skip to main content
본 문서는 백엔드 rate domain 재설계 (refactor/rate-domain-redesign 브랜치, 41 commits) 이후 변경된 GraphQL surface 를 기준으로 프론트엔드 설정화면을 재작업하기 위한 가이드다.

1. 개념 변화 요약

2. UI 흐름 (재확인)

ADR-0002 §2.5/2.6 에서 확정된 UX:
  • 요금제 생성: 5단계 Wizard
  • 일상 운영: Hybrid Grid + Side Panel
  • 변경 영향 관리: 규칙 변경 시 Proposal 알림 → 사용자가 [재물질화 진행 / 부분 적용 / 나중에] 선택

3. RatePlan 생성 — Wizard 5단계

Step 1 — 기본 정보

호출: createRatePlan(accommodationId, input: CreateRatePlanInput)

Step 2 — 기본 가격

Step 2 미리보기: 사용자에게 “토요일 ₩150,000” 같은 결과를 즉시 보여주려면 resolveDailyRate 를 호출 (RatePlan 저장 전이라면 클라이언트 측에서 동일 공식 계산):
pricingRules 는 JSON scalar 이며 다음 shape:

Step 3 — 점유 가격

점유 인원별 가격을 별도 row 로 둔다. baseOccupancy 와 다른 인원 가격을 명시. UI: 1인 / 2인 / 3인 / 4인 등 row 입력. 호출: setRatePlanOccupancyPrices(ratePlanId, prices) — 전량 교체.
pricingMode:
  • absolute: 해당 가격 자체 (예: 1인 ₩60,000)
  • delta_from_base: baseOccupancy 가격 대비 +/- (예: 3인 +₩30,000)

Step 4 — 제약

RatePlan 기본 LOS 와 사전 예약 제한. 날짜별 CTA / CTD / StopSell 은 Step 4 가 아니라 운영 화면의 Side Panel 에서 설정한다 (setRatePlanRestriction / setRatePlanRestrictionBulk).

Step 5 — 포함 서비스 (Inclusion)

RatePlan 에 포함할 inclusion 선택 + 수량/charge override. 호출: setRatePlanInclusions(ratePlanId, inclusions) — 전량 교체.

Wizard 완료

모든 Step 의 input 을 모아 createRatePlan 1회 호출 후, Step 3/5 의 bulk mutation 들을 순차 호출한다. 트랜잭션 보장이 필요하다면 백엔드에 묶음 endpoint 가 별도 필요 (현 시점 미제공).

4. 일상 운영 — Hybrid Grid + Side Panel

좌측 Calendar Grid

호출: getDailyRateCalendar
셀 표기:
  • hasSnapshot=true 인 셀 → ◆ 표시 (확정 snapshot)
  • hasSnapshot=false → 일반 (규칙 계산값)
  • 같은 날 getRatePlanRestrictions 응답 cross-reference 해서 🔒 CTA / 🚫 CTD / ⛔ StopSell 아이콘

Restriction overlay

우측 Side Panel — 단일 셀 편집

  • 가격 수정: setDailyRate(input) 또는 clearDailyRate(...) (snapshot 삭제 → 규칙 계산값 복귀)
  • 제약 수정: setRatePlanRestriction(input) / clearRatePlanRestriction(...)

범위 일괄 수정

SetDailyRateBulkInput:
  • amount / amountDelta / amountPercentDelta 중 1개 택일
  • overrideExisting: false → 기존 snapshot 있는 cell 은 건너뜀 (확정 가격 보호)
  • 응답은 Int (touched cell 수)

5. RateSeason 관리 화면

별도 시즌 카탈로그 화면.
rrule 은 RFC 5545 iCalendar RRULE 문자열 (예: FREQ=WEEKLY;BYDAY=FR,SA). 명시 startDate / endDate 와 함께 사용 가능.

6. Inclusion 관리 화면

postingType:
  • per_stay: 체크인 시 1회 부과
  • per_night: 박당 부과
  • per_person: 1인당 1회
  • per_person_per_night: 1인당 박당
생성/수정/삭제: createInclusion / updateInclusion / deleteInclusion.

7. Rematerialization Proposal 흐름 (중요)

RatePlan 의 defaultAmount / pricingRules / derivationRule 을 수정하면 백엔드는 자동으로 미래 DailyRate snapshot 을 덮어쓰지 않는다 (ADR-0001 §2.2.2). 대신 RematerializationProposal 을 생성하고 사용자 승인을 기다린다.

UI 패턴

  1. 사용자가 updateRatePlan 으로 규칙 변경 → 응답은 정상 (RatePlan row 만 갱신됨)
  2. 프론트는 사용자에게 banner 표시:
    ⚠️ 규칙 변경이 저장되었습니다. 영향 받는 미래 snapshot 47건 / 빈 날짜 683건. [재물질화 진행] [나중에]
  3. 백엔드가 비동기 분석을 끝내면 RematerializationProposal 이 조회 가능해진다.
  4. 사용자가 [재물질화 진행] 클릭 → applyRematerializationProposal(id) 호출
  5. 또는 [나중에] / [폐기] → discardRematerializationProposal(id)

Proposal 조회

Proposal id 는 현재 updateRatePlan 응답에 포함되지 않는다. 다음 두 가지 중 하나:
  • 백엔드가 응답에 proposal id 를 포함하도록 surface 확장을 별도 합의
  • 프론트가 별도 getRematerializationProposals(ratePlanId, status: pending) 형태 list query 를 요청 (현 시점 미제공 — 필요 시 백엔드 PR)

8. Audit 표기 (감사)

엔티티에 lastModifiedBy (User ULID), updatedAt, lastModifiedReason 보조 필드가 있다. Side Panel “이력” 섹션에 표시 권장:
  • “정다운, 2026-05-10 14:32 · 가격 조정 (사유: 성수기 인상)”
전체 변경 이력은 audit-svc 의 별도 query 로 조회한다 (core-svc 는 발행만 함). audit-svc surface 는 audit-svc GraphQL endpoint 별도 호출.

9. Error Codes (UI 처리 가이드)

10. GraphQL Operation 카탈로그

Query (9)

  • getRatePlanList(accommodationId, isActive)
  • getRatePlan(id)
  • getDailyRateCalendar(ratePlanId, roomTypeId, startDate, endDate, occupancy)
  • resolveDailyRate(ratePlanId, roomTypeId, stayDate, occupancy)
  • getRatePlanRestrictions(ratePlanId, startDate, endDate)
  • getInclusionList(accommodationId)
  • getRateSeasonList(accommodationId) / getRateSeason(id)
  • getRematerializationProposal(id)

Mutation (22)

RatePlan 메타: createRatePlan / updateRatePlan / deleteRatePlan DailyRate: setDailyRate / setDailyRateBulk / clearDailyRate / clearDailyRateBulk Restriction: setRatePlanRestriction / setRatePlanRestrictionBulk / clearRatePlanRestriction / clearRatePlanRestrictionBulk RatePlan 하위 전량 교체: setRatePlanOccupancyPrices / setRatePlanInclusions / setRatePlanChannelMappings Inclusion: createInclusion / updateInclusion / deleteInclusion RateSeason: createRateSeason / updateRateSeason / deleteRateSeason Rematerialization: applyRematerializationProposal / discardRematerializationProposal

11. legacy 제거 체크리스트

기존 UI 에서 다음을 모두 제거:
  • ☐ AriPackage 관련 화면 / 컨테이너 개념
  • ☐ AriRate / AriRatePlan / AriPeriod / AriInclusion CRUD 화면
  • ☐ Adjustment(multiplier, adjustment, period) 입력 UI
  • appliedRatePlanId / appliedRateId / appliedAdjustmentFlat / appliedAdjustmentMultiplier 등 legacy 필드 참조
  • ratePlanVersion / rateIdVersion 표시 (BlockComposition archive 는 deprecated)
  • currencySymbol 표시 → currency 코드만 받아 Intl.NumberFormat(locale, { style: 'currency', currency }) 로 클라이언트 변환
신규로 추가할 것:
  • ☑ ratePlanId / roomTypeId 가 ULID 문자열임을 인지 (UUID 와 다름, 26자 Crockford base32)
  • ☑ DailyRate / RatePlanRestriction 의 id 는 GraphQL ID! (string) 이지만 백엔드에서 BigInt — 그대로 string 비교/저장 가능
  • ☑ 가격 입력은 Decimal 스칼라 (string 으로 전송, 부동소수점 손실 방지)
  • pricingRules 는 JSON scalar (객체 직렬화)

12. 참고 ADR / 문서