다크모드
회원가입→첫 부과 — BE 참고
범위
2026-07-14 출시 여정 점검에서 연결한 인증 복구, 상품 변경 게이트, 프로퍼티 수동 면적 스냅샷, Office별 부과기간 저장 계약을 정리한다.
인증 복구
- 요청: Supabase Auth
resetPasswordForEmail(email, { redirectTo: /auth/password-recovery }). - 완료: recovery 세션을 확인한 뒤
updateUser({ password }), 성공 후signOut. - 요청 응답은 계정 존재 여부를 화면에 노출하지 않는다.
상품 변경
product_subscription_items.quantity에서units·users수량을 하이드레이트한다.- 관리 모드의 기준선은 현재 entitlement module 집합과 현재 수량이다.
- module 추가·제거와 수량 변경이 모두 없으면 checkout RPC를 호출하지 않는다.
- 결제수단은 현재
invoice만 as-built다. 카드/PG 계약은 미구현이다.
프로퍼티 면적
update_company_office_property의 기존 p_data_source, p_data를 재사용한다.
- 공공데이터:
p_data_source='DATA_GO_KR_BUILDING_HUB', external code와 원문 스냅샷 저장. - 직접 입력:
p_data_source='MANUAL', external code는 null,property_data.areaTypes[]와areaSummary저장. - 유형 행:
usage,count,exclusiveArea,residentialCommonArea,otherCommonArea, 파생supplyArea,contractArea. - 불변식:
supplyArea=A+B,contractArea=A+B+C; 합계는 각 유형 면적×호실 수의 합.
Office별 부과기간
마이그레이션 20260714070000_secure_billing_period_chain_by_office.sql:
billing_period_chain.office_idFK 추가.(office_id,key)unique.- 기획단
anon full access정책 제거 및 anon 권한 회수. - 조회는 Company 활성 멤버, 쓰기는 Office 멤버 또는 Company 관리자만 허용.
- 기존 office_id 없는 기획 데이터는 삭제하지 않지만 RLS상 접근 불가다.
클라이언트 key는 service-charge@{officeUuid}:actual 형태다. UUID가 아닌 로컬 데모 Office는 기존 service-charge:actual 키를 유지하고 원격에는 쓰지 않는다.
첫 부과 준비 게이트
운영 UUID Office는 다음 조건을 모두 충족할 때만 첫 정기 관리비를 만들 수 있다.
property_data_source가DATA_GO_KR_BUILDING_HUB또는MANUAL이고property_data.areaTypes[]가 존재한다.- Office의
unit_count와 부과 기초데이터 유닛 수가 일치한다. - 활성 부과항목이 1개 이상이다.
- 활성 유닛마다 현재 청구 대상 멤버 또는 매출처가 정확히 1개 지정돼 있다.
클라이언트는 각 미충족 조건을 설정 화면 바로가기와 메뉴 경로로 표시한다. 이 메타데이터는 안내용이며 서버 권한을 우회하지 않는다. /administration/organization/workspace/property, /administration/organization/workspace/property/console/unit, /service-charge/setting/item/general은 기존 인증·Company·Office 권한 계약을 그대로 적용한다. 권한이 없는 사용자는 표시된 메뉴 경로를 Company 관리자에게 전달해 권한을 요청해야 한다.
선도 제품은 현재 클라이언트 정본을 조합해 준비상태를 계산한다. 실제 제품이 같은 게이트를 적용하려면 목록 조회 또는 전용 조회 응답이 아래 서버 소유 상태를 제공해야 한다. 서버는 화면 문구나 프론트엔드 route를 반환하지 않고 안정적인 code와 판정 근거만 반환한다.
json
{
"first_charge_readiness": {
"ready": false,
"blockers": [
{
"code": "SUBJECT_ROSTER_INCOMPLETE",
"current_count": 412,
"required_count": 466
}
]
}
}허용 blocker 코드는 PROPERTY_AREA_MISSING, UNIT_MASTER_INCOMPLETE, CHARGE_ITEM_MISSING, SUBJECT_ROSTER_INCOMPLETE다. 실제 제품의 /feeV2/maintenance-fee 응답에는 아직 이 계약이 없으므로 프론트엔드가 행 개수나 기존 정산 유무로 준비상태를 추정해 생성 동작을 차단하면 안 된다. 계약 도입 뒤 생성 API도 같은 판정을 재검증하고 미충족이면 409 FIRST_CHARGE_NOT_READY와 동일 blocker 목록을 반환해야 한다.
정기 관리비 초안 삭제 불변식
물리 삭제는 다음 조건을 모두 만족하는 첫 단일 초안에만 허용한다.
billing_period_chain.chain.entries가 정확히 1개이고currentIndex=0- 엔트리
status='open' - 연결된
service_charge_stage_workflows가 없거나 네 단계가 모두pending - 해당 workflow의 전이 감사행이 0개
선도 제품 Supabase는 20260723090000_guard_service_charge_draft_period_delete.sql의 before delete trigger가 이 조건을 서버에서 재검증한다. 조건을 벗어나면 service_charge_period_delete_not_draft 또는 service_charge_period_delete_after_start_forbidden으로 삭제를 거부한다. 모든 단계가 pending인 빈 workflow만 초안과 함께 정리하며, 전이·확정·청구·수납 이력이 있는 workflow는 삭제하지 않는다.
실제 제품의 DELETE /feeV2/maintenance-fee/{id}도 같은 불변식을 서버에서 소유해야 한다. UI의 disabled checkbox는 편의 게이트일 뿐 보안·감사 불변식이 아니다. 부과 시작 이후 제거 요구는 물리 삭제 대신 취소·재오픈·무효화 이벤트로 처리하고 원행을 보존한다.
property_data.units[]는 선택한 건축물대장 스냅샷 안에 Office와 함께 영속된다. 클라이언트는 운영 UUID Office에서 이 배열을 unitCode=dongName-unitName인 부과 유닛 정본으로 변환한다. 변환 수가 offices.unit_count와 다르면 첫 부과를 차단한다. 운영 Office로 전환할 때 기획용 데모 유닛·계약·멤버·금액은 모두 제거한다.
운영 Office의 첫 부과 원자는 활성 부과항목 × 실제 유닛으로 만든다. 아직 계약·멤버를 등록하지 않은 단계이므로 각 원자의 contractCode, memberCode는 null이고 유닛 축만 준비한다. 면적 기준값은 유닛 스냅샷의 exclusiveArea, contractArea를 사용하며 사용량·고정지분·가변지분은 0에서 시작한다. 부과항목 금액도 전부 0, targetCount는 실제 유닛 수로 초기화한다. 따라서 demoSeed의 15세대·225만원 시나리오가 운영 Office에 노출되거나 배분되면 안 된다.
현재 기간 부과총액이 0인 동안 다른 prototype 기간의 receipt·aging seed를 첫 부과 개요 KPI로 노출하지 않는다. 클라이언트는 수납·미수·연체 지표를 미산정 상태로 표시하며, 장기적으로는 receipt·aging 저장소도 office_id + period_id 범위로 조회해야 한다.
모바일과 데스크톱은 같은 회계형 부과산정 표와 동일한 부과항목 원자·커밋 계약을 사용한다. viewport는 표 컨테이너의 좌우 스크롤만 바꾸며 응답 열, 부과 API, 임시 저장소 또는 모바일 전용 DTO를 만들지 않는다.
유닛명세 소비 계약은 유닛을 행 엔티티로 유지한다. 수백 호실을 동적 응답 필드나 가로 피벗 열로 만들지 않고 unitCode 기준 행 배열과 dongName, floorName, unitName, 항목별 배분액을 제공해야 한다. 공공데이터 원문과 안정 식별용 unitCode는 보존하되 표시용 dongName 앞에 Office 이름이 반복되면 클라이언트 정규화 단계에서 제거한다. 고객 표시명 동-층-호는 세 원자 필드에서 파생하며 DB 고유식별자로 사용하지 않는다.
계약명세와 멤버명세도 각각 계약·멤버를 행 엔티티로 유지한다. 계약·멤버가 0건이면 활성 부과항목을 대상 데이터처럼 반환하지 않고 빈 컬렉션을 반환한다. 수백 계약·멤버를 가로 피벗 열로 만들지 않는다. 명세 직접 진입·새로고침도 Office 식별자를 먼저 동기화하며, 운영 UUID Office에 데모 계약·멤버·금액을 fallback하지 않는다.
유닛·계약·멤버 명세는 모두 현재 Office와 같은 Office-scoped 부과기간을 소비한다. 탭별 정적 데모 프로퍼티명이나 기간을 응답 계약으로 두지 않는다.
면적 유형만 저장한 MANUAL 스냅샷에는 실제 동·호가 없으므로 areaTypes[].count를 가짜 유닛으로 확장하지 않는다. 별도 호실 마스터 입력이 완료되기 전에는 준비 게이트가 닫힌다.
아직 필요한 BE 작업
- 카드/PG 결제수단 및 승인·실패·환불 계약.
- Leyve 플랫폼 공용 운영 Secret
DATA_GO_KR_SERVICE_KEY를 서버에 한 번 주입한다. Company·Office별 키를 저장하거나 사용자에게 입력받지 않는다. - 부과항목·유닛별 사용량·고정지분·가변지분의 Office 영속 테이블 정규화. 현재 면적은 Office 스냅샷, 나머지 값은 프로토타입 캐논이다.