# B2B 계층 / Role / 온보딩 (v0)

## Role 정의

| Role | 스키마 | 권한 |
|---|---|---|
| owner | `center_memberships.role='owner'` | 샵 내 모든 리소스 read/write, 트레이너/멤버 초대, 샵 정보 수정 |
| trainer | `center_memberships.role='trainer'` | 샵 내 member 리스트 read, 회원 초대, supervised 세션 기록/수정 |
| member | `center_memberships.role='member'` | 본인 세션 full read/write, 다른 리소스 접근 불가 |

샵 당 owner는 **정확히 1명** (`UNIQUE INDEX one_owner_per_center`).
한 auth.user가 여러 샵에 동시 소속 가능하지만 v0 UI는 단일 샵만 표시 (가장 최근 `joined_at`).

## 권한 매트릭스

| 행동 | Member | Trainer | Owner |
|---|:---:|:---:|:---:|
| 본인 self 세션 생성 | O | O (member로도 작동) | O |
| Supervised 세션 생성 (다른 회원 대신) | X | O (같은 샵 member에 한해) | O |
| 본인 세션 read | O | O | O |
| 같은 샵 member 세션 read | X | O | O |
| 다른 샵 member 세션 read | X | X | X |
| 자기가 진행한 supervised 세션 수정 | — | O | O |
| 타인의 세션 삭제 | X | X | X (v0) |
| 샵 생성 | O | O | O (본인이 owner가 됨) |
| Trainer 초대 | X | X | O |
| Member 초대 | X | O | O |
| 샵 정보(name) 수정 | X | X | O |

## 온보딩 플로우

### A. Owner (샵 최초 생성)

```
1. 일반 회원가입 (email + password) via LoginScreen
2. 로그인 성공 → _AuthGate 실행
   - center_memberships 조회 → 빈 결과
   - 기존: ScanScreen으로 바로 진입 (B2C)
   - 신규: ScanScreen 상단에 "샵 운영하시나요? 샵 생성하기" 배너 표시
3. Owner가 "샵 생성" → CreateShopScreen
   - input: 샵 이름
   - action: centers insert + center_memberships(owner) insert (트랜잭션 또는 RPC)
4. 성공 시 ShopHomeScreen으로 이동
```

### B. Trainer (Owner가 초대)

```
1. Owner가 ShopHomeScreen → "트레이너 초대" → InviteMemberScreen(role=trainer)
   - input: email
2. Edge function invite_member 호출
   - 해당 email의 auth.user 존재 여부 확인
     - 없음: admin.auth.admin.createUser({ email, email_confirm: false }) → shadow 상태
     - 있음: 기존 user.id 재사용
   - pending_invitations row 생성 (shadow_user_id 기록)
   - center_memberships(center_id, user_id=shadow, role=trainer) insert
   - (v0 email 발송 생략)
3. Owner가 초대된 trainer의 email과 "임시 접속 방법"을 수동으로 전달
   - v0: Supabase Auth "Send magic link" 관리 콘솔에서 수동 트리거
   - v1: 자동 이메일
4. Trainer가 magic link로 최초 로그인 → 비밀번호 설정 → ShopHomeScreen
```

### C. Member (Trainer 또는 Owner가 추가)

```
1. Trainer가 ShopHomeScreen → "멤버 추가" → InviteMemberScreen(role=member)
   - input: email (+ 선택: 이름)
2. Edge function invite_member 호출
   - auth.user 생성 (email_confirm: false)
   - center_memberships(center_id, user_id=shadow, role=member) insert
   - pending_invitations row 생성
3. 이후 shadow 상태로 유지 — member가 직접 로그인하지 않아도 trainer가 supervised 세션을 기록 가능
4. (v1) member가 email로 로그인 시 기존 shadow user가 그대로 실사용자로 활성화
```

### D. 기존 B2C Member가 샵에 합류 (v0에서는 수동)

```
v0: Owner/Trainer가 member의 email을 invite_member에 넣으면 auth.user 이미 존재 → 기존 user.id로 center_memberships만 insert.
    Member의 기존 self 세션 데이터는 그대로 유지. 이후 supervised 세션도 누적됨.
v1: member 앱에 "샵 코드로 가입" 기능.
```

## 세션 소유권 규칙

| 경로 | user_id | trainer_id | center_id | session_type |
|---|---|---|---|---|
| Member 직접 로그인 → 자율 운동 | member auth.uid() | NULL | NULL | self |
| Trainer가 샵에서 member 선택 → 수업 | member의 user.id | trainer auth.uid() | center_id | supervised |
| Owner가 직접 수업 진행 | member의 user.id | owner auth.uid() (trainer로 작용) | center_id | supervised |
| Member가 샵 내에서 본인 로그인으로 자율 운동 | member auth.uid() | NULL | NULL (또는 샵 방문 의미 부여 시 center_id) | self |

v0은 마지막 행에서 center_id를 NULL로 둔다 (샵 방문 체크인 기능 없음).

## 앱 내 Role 분기 (lib/main.dart)

```
_AuthGate:
  isDevMode? → DevHomeScreen (기존)
  !isSignedIn? → LoginScreen (기존)
  isSignedIn →
    currentRoleProvider 조회 (center_memberships로 판단)
    ├─ null or 'member' → ScanScreen (B2C 유지)
    ├─ 'trainer' → ShopHomeScreen
    └─ 'owner' → ShopHomeScreen (트레이너 초대 버튼 추가 표시)
```

### currentRoleProvider 구현 개요

```dart
@riverpod
Future<MembershipRole?> currentRole(CurrentRoleRef ref) async {
  final userId = ref.watch(currentUserProvider)?.id;
  if (userId == null) return null;
  final repo = ref.watch(shopRepositoryProvider);
  final memberships = await repo.membershipsForUser(userId);
  // 여러 센터 중 가장 최근 joined 선택 (v0)
  return memberships.isEmpty
      ? null
      : memberships.first.role; // sorted by joined_at desc
}
```

## UI 화면 목록

| 화면 | 대상 Role | 위치 |
|---|---|---|
| LoginScreen | - | 기존 `lib/features/auth/presentation/screens/login_screen.dart` |
| CreateShopScreen | 아직 샵 없는 사용자 | `lib/features/shop/presentation/screens/create_shop_screen.dart` |
| ShopHomeScreen | owner, trainer | `lib/features/shop/presentation/screens/shop_home_screen.dart` |
| InviteMemberScreen | owner (trainer+member 초대) / trainer (member만) | `lib/features/shop/presentation/screens/invite_member_screen.dart` |
| MemberDetailScreen | owner, trainer | `lib/features/shop/presentation/screens/member_detail_screen.dart` |
| WorkoutFlowScreen (supervised mode) | owner, trainer | `lib/features/workout/presentation/screens/workout_flow_screen.dart` (수정) |
| HistoryScreen | 모든 role | 기존 유지, trainer view에서 member 필터 제공 |
