다크모드
Leyve Sales Center 프런트엔드 메인테이너 계약
라우트와 구성
- 라우트:
/headquarters/sales/customers - View:
src/views/headquarters/sales/IndexView.vue - Page:
src/components/headquarters/sales/SalesCustomerAccountsPag.vue - 생성 Sheet:
src/components/headquarters/sales/SheetCreateSalesCustomerArt.vue - 상태:
src/composables/useSalesCustomerAccounts.js - repository seam:
src/composables/salesCustomerAccountRepo.js
본사 Sales Center는 현재 Console과 인증·배포 경계가 같으므로 Console 안에 둔다. 외부 파트너 전용 Partner Center와 화면 저장소는 분리하지만 두 화면이 고객 데이터를 복사하지 않고 동일 backend RPC를 소비한다.
데이터 흐름
- composable이 seller 목록을 읽고 전역관리 가능한
first_party판매주체를 기본 선택한다. - 목록은 선택 판매주체, 검색어와 lifecycle 상태를
list_sales_customer_accounts_v2에 전달한다. - 생성 Sheet의 실제 유입 경로는 판매주체와 독립된 공통 선택지다.
- 본사 직원이 폼에서 만드는 고객은
first_party_assisted, 파트너가 만드는 고객은channel_partner_assisted로 파생한다. 고객 직접가입은 별도 webhook/adapter가self_service로 기록한다. - 고객계정 생성과 선택적 프로비저닝 요청은 서로 다른 idempotency key를 사용한다.
- 실패 후 같은 입력으로 재시도할 때는 완료될 때까지 같은 command key를 재사용한다.
sales_customer_events의 INSERT를 Realtime invalidation으로 구독한다.- 이벤트 payload로 목록 행을 직접 만들거나 수정하지 않고
list_sales_customer_accounts_v2를 다시 호출한다. get_my_sales_management_capabilities_v1의canApprovePartners가 참일 때만 신청을 읽고, 검토 메모 확인 다이얼로그에서approve_partner_application_v1을 호출한다.canOperateProvisioning이 참일 때만list_sales_provisioning_operations_v1을 읽고 프로비저닝 운영 카드를 렌더링한다.- 수동 재시도 확인 다이얼로그는 선택 행의
kind,queueItemId, 현재attempts와 필수 사유를retry_sales_provisioning_operation_v1에 전달한다. - 성공 뒤 고객 목록·운영 목록·필요 시 파트너 신청 목록을 다시 읽는다. stale/busy 오류는 로컬 상태로 덮어쓰지 않고 새로고침을 안내한다.
Supabase 설정이 없으면 prototype repository를 사용한다. 이 저장소는 UI·업무 흐름 검증용이며 운영 동시성·다중 브라우저 영속을 제공하지 않는다.
판매주체 UI 규칙
- 본사 직판·직접가입:
first_party판매주체 - 파트너 판매·서비스: 실제
channel_partner판매주체 - 본사 전역 사용자는 파트너를 대신해 생성할 수 있지만, 귀속은 선택한 실제 파트너로 기록한다.
본사용 파트너옵션을 추가하지 않는다.- 최초 귀속과 현재 담당배정을 같은 Select로 덮어쓰지 않는다.
Partner Center 연결
Partner Center adapter는 로그인 세션에서 seller organization을 고정한다. 사용자가 임의의 판매주체 ID를 입력하지 않게 하고, backend membership/RLS가 다시 검증한다. 본사 화면만 전역관리 권한으로 seller 범위를 전환할 수 있다.
파트너 승인 뒤 worker가 channel_partner 판매주체와 최초 admin membership을 만들면 get_my_sales_seller_context_v1의 결과에 나타난다. 승인 화면은 seller ID를 생성하거나 추측하지 않는다.
Sales Center의 파트너 신청 카드는 backend capability canApprovePartners가 참인 경우에만 렌더링한다. seller 목록의 canManageAll은 조회 범위 표시에 쓰일 수 있지만 승인 권한 근거가 아니다. 승인 버튼은 submitted | under_review 행에만 표시하고 검토 메모가 비어 있으면 확인을 비활성화한다. 최종 권한·상태·공백 메모 검증은 approve_partner_application_v1이 담당한다.
프로비저닝·Realtime 경계
- 브라우저는
request_sales_customer_provisioning_v1까지만 호출한다. Company/Auth/Office 생성 RPC와 Edge worker는service_role전용이다. - 기존·신규 Auth 사용자의 Company·Office membership은 모두 처음에
invited다./auth/company-select가list_my_pending_sales_company_invitations_v1로 자기 초대를 표시하고 사용자가 확인 다이얼로그에서accept_sales_company_invitation_v1을 직접 호출해야 활성화된다. - pending 초대는 활성 Company가 0·1·여러 개인 경우 모두 표시한다. 초대가 있으면 단일 Company 자동 진입을 억제해 초대를 숨기지 않는다. 수락 RPC 중에는 ESC·backdrop·닫기 버튼으로 다이얼로그를 닫을 수 없다.
- 목록의 고객 lifecycle
active는 프로비저닝 완료,administratorInvitationStatus는 관리자 수락 대기/완료로 분리해 표시한다. - Realtime 채널은
public.sales_customer_events, event는INSERT만 사용한다. RLS가 현재 seller가 읽을 수 있는 고객 이벤트만 허용한다. - 이벤트 callback은 payload를 폐기하고 composable의 현재 seller·검색어·상태 필터로 전체 목록을 다시 조회한다. 같은 microtask 안의 연속 invalidation은 한 번으로 합친다.
- component unmount에서 먼저 disposed 상태를 세우고 채널을 제거한다. async 초기화 완료 뒤 subscribe하거나 microtask에 대기 중인 reload도 disposed면 폐기한다. injected/prototype repository에 Realtime API가 없으면 안전한 no-op이다. Realtime은 전달 보장 원장이 아니므로 재진입 시 초기 조회가 정본을 복구한다.
- 운영 목록은 customer event Realtime invalidation 때 고객 목록과 함께 재조회한다. partner outbox 자체는 private이므로 이벤트가 없거나 연결이 끊긴 경우 카드의 새로고침과 페이지 재진입이 정본을 복구한다.
- 운영 상태 라벨은
SALES_PROVISIONING_OPERATION_STATES, 유형 라벨은SALES_PROVISIONING_OPERATION_KINDS, DTO 정규화는normalizeSalesProvisioningOperation한곳에서 관리한다. - prototype repository는 실패 customer와 10회 소진 partner 예시를 제공하고, 수동 재시도 시 attempts 0·pending·감사 횟수 증가를 모사한다. 운영 동시성의 실제 보장은 SQL RPC 테스트가 담당한다.
화면을 공유해야 한다면 page 전체를 복사하지 말고 repository DTO, 상태 라벨과 폼 schema를 공통 package로 승격한다. Console/Center의 app shell과 권한 피드백은 각 표면에 남긴다.
제품 표면 이름
console: 다제품·조직 통제면center: Partner/Sales처럼 특정 직무의 집중 업무면portal: 개인 셀프서비스kiosk: 현장 공용 단말
저장소 물리 rename은 이번 구현 범위가 아니다. 명명 결정은 docs/decisions/SURFACE-REPOSITORY-NAMING-2026-07-19.md를 따른다.
테스트
src/composables/__tests__/salesCustomerAccountRepo.spec.jssrc/composables/__tests__/useSalesCustomerAccounts.spec.jssrc/composables/__tests__/salesDistributionCoreMigration.spec.jssrc/composables/__tests__/salesProvisioningWorker.spec.jssrc/composables/__tests__/salesProvisioningOperationsMigration.spec.jssrc/composables/__tests__/companySignupRepo.spec.jssrc/router/__tests__/salesCenterRoute.spec.jssupabase/tests/sales_distribution_core_smoke.sqlsupabase/tests/sales_partner_provisioning_worker_smoke.sql