# 글로뷰 시스템 아키텍처 및 생애주기 가이드 (GLOBEAU Architecture & Lifecycle Guide)

글로뷰 뷰티 플랫폼의 핵심 도메인 상태값 전이(Lifecycle), 권한 체계, 미디어 에셋 파이프라인, 프로그램 및 패키지 번들 엔진, 주문/결제 및 디지털 티켓 보관함, 위치기반(LBS) 엔진 및 모바일 QR 티켓 시스템, 뷰티샵 방문 예약 및 현장 QR 인증/차감 파이프라인, 뷰티샵 관리자 다매장 세션 및 온보딩 미들웨어 체인, 메시징/알림 템플릿 및 감사 로깅 서브시스템, 다중 소셜 로그인(Kakao, Naver, Google) 및 계정 자동 통합 엔진, 마이페이지 회원정보 수정 파이프라인(프로필 관리/동적 SMS 본인인증/소셜 및 자체 비밀번호 분기/Flatpickr 생년월일/Daum 우편번호 인라인 검색), 기술 자산 명세를 정의합니다.

---

## 1. 도메인 상태값 생애주기 (Domain Status Lifecycle)

### (1) 회원 및 인증 (User & Auth)
카카오·네이버·구글 간편 로그인을 통한 최소 정보 수집 후 비즈니스 액션(주문/결제, 입점 신청, 회원정보 수정) 시점에 추가 인증을 수행하는 **점진적 온보딩(Progressive Onboarding)**을 채택합니다.

* **회원 상태 (`users.status`)**
  * `active`: 정상 활동 회원 (기본값)
  * `suspended`: 운영 제재 또는 정지된 회원 (소셜 로그인 즉시 차단)
  * `withdrawn`: 탈퇴 회원 (소프트 딜리트 `deleted_at` 병행, 소셜 로그인 시 재가입 방어)

* **회원 상태 전이 흐름**
  * [방문자] ──▶ 소셜 간편 로그인 (Kakao / Naver / Google) ──▶ [User: active (phone: null)]
  * [User: active] ──▶ [입점 신청, 주문 결제, 또는 마이페이지 프로필 수정] ──▶ SMS 휴대전화 본인인증 완료 ──▶ [phone 등록 및 is_phone_verified = true]
  * [User: active] ──▶ [회원 탈퇴 (`ProfileController@destroy`)] ──▶ [status: withdrawn (softDeletes)]

### (2) 뷰티샵 심사 및 영업 (Shop)
입점 승인 여부(`status`)와 실제 영업 노출 여부(`operating_status`)를 독립된 플래그로 분리 관리합니다.

* **심사 상태 (`shops.status`)**
  * `pending`: 입점 신청 직후 대기 (기본값)
  * `approved`: 최고관리자 심사 통과 (원장 계정에 Role `shop` 동기화)
  * `rejected`: 심사 반려 (`rejection_reason` 명시)

* **영업 상태 (`shops.operating_status`)**
  * `suspended`: 운영 정지 (신청 초기 기본값, 미승인 상태, 본사 제재)
  * `active`: 정상 영업 및 서비스 노출
  * `temp_closed`: 원장 설정에 따른 임시 휴무
  * `closed`: 폐업 처리

* **뷰티샵 상태 전이 흐름**
  * [입점 신청 접수] ──▶ status: pending / operating_status: suspended
  * [본사 심사 반려] ──▶ status: rejected / operating_status: suspended
  * [본사 심사 승인] ──▶ status: approved / operating_status: active
  * [원장 임시 휴무] ──▶ operating_status: temp_closed
  * [본사 운영 제재] ──▶ operating_status: suspended

### (3) 시술 프로그램 판매 및 심사 (Program)
* **프로그램 상태 (`programs.status`)**
  * `draft`: 임시 작성 상태
  * `pending`: 본사 심사 대기
  * `approved`: 본사 승인 완료 (판매 및 예약 노출 가능)
  * `rejected`: 심사 반려 (`rejection_reason` 명시)

### (4) 주문 및 티켓 상태 (Order & User Ticket)
* **주문 상태 (`orders.status`)**
  * `pending`: 결제/입금 대기 (무통장 계좌이체 선택 시 최대 7일간 대기, `deposit_due_at`)
  * `paid`: 결제 완료 (카드 결제 즉시 또는 관리자/웹훅 입금 확인 시 전환, 티켓 자동 발급)
  * `cancelled`: 주문 취소 (미입금 기한 만료 등)
  * `refunded`: 환불 완료
* **티켓 마스터 상태 (`user_tickets.status`)**
  * `active`: 정상 사용 가능
  * `exhausted`: 하위 모든 티켓 아이템 소진 완료 (`remaining_count = 0`)
  * `expired`: 티켓 유효 만료일(`expires_at`) 도래
  * `cancelled`: 결제 취소 또는 환불로 인한 전체 회수
* **티켓 아이템 단위 상태 (`user_ticket_items.status`) - 실제 예약 및 차감 대상**
  * `active`: 예약 및 이용 가능 잔여 회차 보유 (`remaining_count > 0`)
  * `exhausted`: 해당 프로그램 회차 소진 완료 (`remaining_count = 0`)

### (5) 뷰티샵 방문 예약 상태 (Reservation Lifecycle)
디지털 티켓 아이템(`user_ticket_items`)을 사용하여 특정 뷰티샵에 방문 일정을 신청하고 차감 완료되는 상태 흐름입니다.

* **예약 상태 (`reservations.status`)**
  * `pending`: 고객이 일자/슬롯을 선택해 예약 신청 완료 (예약 접수 대기 상태)
  * `confirmed`: 뷰티샵 원장이 일정 확인 후 예약 승인 확정
  * `completed`: 고객 매장 방문 및 QR 스캔 인증을 통해 시술 완료 및 **회차 차감(`remaining_count - 1`)** 완료
  * `cancelled`: 고객 사전 취소 또는 원장 승인 반려 (차감 없이 슬롯 반환)
  * `noshow`: 사전 연락 없이 예약 시간에 미방문 (샵 정책 `after_deadline_policy`에 따라 차감 여부 결정)

* **예약 상태 전이 흐름**
  * [고객 예약 신청] ──▶ status: pending
  * [원장 반려 또는 고객 사전 취소] ──▶ status: cancelled (슬롯 반환)
  * [원장 일정 승인] ──▶ status: confirmed
  * [현장 모바일 QR 스캔 및 시술 완료] ──▶ status: completed (해당 user_ticket_item 잔여 회차 1회 차감)
  * [고객 미방문 노쇼] ──▶ status: noshow (샵의 패널티 정책에 따라 차감 또는 유지)

---

## 2. 다중 소셜 로그인 및 계정 자동 통합 엔진 (Multi-Social Login & Identity Merging)

글로뷰는 사용자 편의성과 전환율을 극대화하기 위해 국내 3대 소셜 인증(카카오, 네이버, 구글)을 통합 제공하며, 주문/티켓/예약 데이터 파편화를 원천 차단하는 계정 통합 아키텍처를 가집니다.

### (1) 소셜 계정 데이터베이스 7단 표준 스키마

#### 1. 회원 마스터 (`users`)
> **설계 원칙**: `users` 테이블은 순수 개인 프로필 및 계정 자산 마스터이며, **뷰티샵 위치 좌표(`latitude`, `longitude`, `region_*`) 컬럼을 포함하지 않습니다.**

| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 회원 고유 식별 번호 |
| **2** | - | `login_id` | String(100) | Y | null | 레거시 호환용 로그인 ID (Unique) |
| **3** | - | `email` | String(255) | Y | null | 이메일 (소셜/일반 공통 식별키, Unique, 인덱스) |
| **4** | - | `password` | String(255) | Y | null | 비밀번호 (소셜 최초 가입 시 32자리 난수 해시 저장, 자체 비밀번호 등록 시 덮어쓰기) |
| **5** | - | `name` | String(100) | N | - | 실명 또는 소셜 프로필명 |
| **6** | - | `birth_date` | Date | Y | null | 생년월일 (YYYY-MM-DD, Flatpickr 연동) |
| **7** | - | `gender` | String(1) | Y | null | 성별 (`M`: 남성, `F`: 여성, `U`: 미선택) |
| **8** | - | `phone` | String(20) | Y | null | 휴대폰 번호 (하이픈 제외 숫자, Unique, 인덱스) |
| **9** | - | `zipcode` | String(10) | Y | null | 기본 배송지 우편번호 |
| **10** | - | `address1` | String(255) | Y | null | 기본 배송지 도로명 주소 |
| **11** | - | `address2` | String(255) | Y | null | 상세 주소 |
| **12** | - | `profile_photo_path` | String(255) | Y | null | 프로필 사진 URL / 스토리지 경로 |
| **13** | - | `ci` | String(255) | Y | null | 본인인증 고유 연계정보 (외부 실명인증 연동용) |
| **14** | - | `is_identity_verified`| Boolean | N | `false` | 외부 실명 본인인증 완료 여부 |
| **15** | - | `is_phone_verified` | Boolean | N | `false` | 휴대폰 SMS 본인인증 완료 여부 (주문/입점/정보수정 가드) |
| **16** | - | `is_email_verified` | Boolean | N | `false` | 이메일 인증 여부 |
| **17** | - | `identity_verified_at`| Timestamp | Y | null | 실명 인증 일시 |
| **18** | - | `phone_verified_at` | Timestamp | Y | null | 휴대폰 인증 일시 |
| **19** | - | `email_verified_at` | Timestamp | Y | null | 이메일 인증 일시 |
| **20** | - | `is_admin_registered`| Boolean | N | `false` | 관리자 수동 생성 여부 |
| **21** | - | `agrees_to_sms` | Boolean | N | `true` | SMS / 알림톡 마케팅 수신 동의 |
| **22** | - | `agrees_to_email` | Boolean | N | `true` | 이메일 소식지 마케팅 수신 동의 |
| **23** | - | `admin_memo` | Text | Y | null | 관리자용 회원 메모 |
| **24** | - | `status` | String(20) | N | `'active'` | 계정 상태 (`active`: 정상, `suspended`: 정지, `withdrawn`: 탈퇴) |
| **25** | - | `last_login_at` | Timestamp | Y | null | 최근 로그인 일시 |
| **26** | - | `remember_token` | String(100) | Y | null | 자동 로그인 토큰 |
| **27** | - | `created_at` | Timestamp | Y | null | 가입 일시 |
| **28** | - | `updated_at` | Timestamp | Y | null | 수정 일시 |
| **29** | - | `deleted_at` | Timestamp | Y | null | 소프트 딜리트 탈퇴 일시 (복구 보존) |

#### 2. 소셜 계정 매핑 테이블 (`social_accounts`)
| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 연동 레코드 고유 번호 |
| **2** | **FK** | `user_id` | Bigint (unsigned) | N | - | 회원 마스터 ID (`users.id`, `cascadeOnDelete`) |
| **3** | - | `provider` | String(50) | N | - | 소셜 제공자 (`kakao`, `naver`, `google`) |
| **4** | - | `provider_id` | String(255) | N | - | 소셜 플랫폼 고유 사용자 식별값 |
| **5** | - | `created_at` | Timestamp | Y | null | 연동 일시 |
| **6** | - | `updated_at` | Timestamp | Y | null | 수정 일시 |

> **고유키 제약조건**: `['provider', 'provider_id']` 복합 Unique 인덱스 적용으로 특정 SNS 계정의 중복 연동을 차단합니다.

### (2) 소셜 가입자의 비밀번호 메커니즘 및 듀얼 로그인 지원
1. **최초 가입 시 난수 비밀번호 주입 이유**: `users.password` 컬럼의 무결성(NOT NULL) 보장 및 외부 침입자의 일반 로그인 창 무작위 대입 방어를 위해, 시스템 내부에서 `Hash::make(Str::random(32))` 난수를 생성해 최초 1회 저장합니다. 이 값은 이후 소셜 로그인을 반복해도 변경되지 않습니다.
2. **소셜 로그인 독립성**: 소셜 인증은 OAuth2 프로바이더 ID(`social_accounts.provider_id`)를 대조하여 세션을 발급하므로, 비밀번호의 유무나 변경 여부와 완전히 무관하게 작동합니다.
3. **듀얼 로그인(소셜 간편인증 + 자체 ID/PW) 활성화**: 소셜 회원이 마이페이지 정보수정에서 자체 비밀번호를 등록하면 기존 난수 해시가 고객의 비밀번호 해시로 교체됩니다. 이후 해당 계정은 **소셜 원클릭 로그인**과 **일반 이메일/비밀번호 로그인**을 모두 자유롭게 이용할 수 있습니다.

### (3) 계정 자동 통합 및 트랜잭션 동시성 제어 흐름
어제 카카오로 결제한 회원이 오늘 네이버나 구글로 재로그인하더라도, 하나의 회원(`User`)으로 자동 통합되어 주문 및 티켓 보관함이 완벽히 유지됩니다.

* **1단계 (기존 연동 확인)**: 소셜 콜백 인입 시 `social_accounts` 테이블에서 `(provider, provider_id)` 검색
  * 일치 데이터 존재 시 ──▶ 계정 활성 상태(`status === 'active'`) 검증 후 `Auth::login($user)` 및 최근 로그인 일시(`last_login_at`) 갱신
* **2단계 (신규 소셜 진입 및 DB 트랜잭션)**: 연동 기록이 없는 경우 `DB::transaction` 및 `User::lockForUpdate()` 진입
  * 이메일(`email`) 대조 결과 기존 가입자가 존재하는 경우: 기존 회원 정보 유지, 프로필 사진 보강 후 `social_accounts`에 `[user_id, provider, provider_id]` 레코드 추가
  * 이메일이 없거나 신규 회원인 경우: `users` 테이블에 신규 회원 생성(`status='active'`, Spatie `user` 역할 부여) 후 `social_accounts`에 연동 레코드 추가
* **3단계 (인증 완료)**: `Auth::login($user)` 실행 및 세션에 저장된 원래 이동 대상 페이지(`intendedUrl`)로 리다이렉트

### (4) 소셜 프로바이더 규격 및 라우팅 인프라
* **공용 라우트 패턴 (`routes/web.php`)**: 단 2줄의 공용 라우트로 3대 소셜사를 통합 라우팅하며 `whereIn` 가드로 인가되지 않은 프로바이더 요청을 차단합니다.
* **서비스 및 패키지 환경**:
  * 카카오 및 네이버: `socialiteproviders/kakao`, `socialiteproviders/naver` (`AppServiceProvider` Event Listener 방식 동적 확장)
  * 구글: `laravel/socialite` 코어 드라이버 내장 활용
  * 환경 변수(`config/services.php`): `kakao`, `naver`, `google` 각각 `client_id`, `client_secret`, `redirect` 슬롯 완비

---

## 3. 다중 매장 소유 및 권한 체계 (Multi-Shop & Role Architecture)

### (1) 역할 기반 접근 제어 (Spatie RBAC System)
회원(`User`)은 여러 개의 역할(`Role`)을 복합적으로 보유(`syncRoles`)할 수 있습니다.

| 역할 (Role) | 주요 권한 및 접근 URL | 비고 |
| :--- | :--- | :--- |
| `admin` | 본사 최고 관리자 (`/admin`) | 플랫폼 전체 통제, 입점 승인, 프로그램 본사 배포, 주문/정산 통제 (PC 중심 UI) |
| `operator` | 본사 부운영자 (`/admin`) | 메뉴별 세부 권한 제어 |
| `center` | 뷰티센터 관리자 | 권역별 거점 지사 관리 |
| `shop` | 뷰티샵 원장/매니저 (`/shop-admin`) | 샵 상세 정보, 배정 프로그램 관리, 예약 접수/확정 및 QR 스캔 차감 (모바일 최적화) |
| `manufacturer` | 파트너 제조사 | 제품 및 원자재 공급 관리 |
| `user` | 일반 고객 (`/`) | 프로그램 탐색, 주문/결제, 모바일 티켓 보관함, LBS 기반 샵 예약 (기본 가입 권한) |

### (2) 1인 다매장 소유 구조 (1:N Multi-Shop Ownership)
* **관계 정의**: `User (1)` <-> `Shop (N)` (`shops.user_id`)
* **다중 매장 전환 세션 (Active Shop Session)**:
  * 원장이 보유한 여러 매장 중 현재 제어 중인 대상은 `session('active_shop_id')`에 저장합니다.
  * 상단 Topbar 드롭다운에서 매장 변경 시 `/shop-admin/switch-shop/{shop}` 라우트를 통해 소유권 확인 후 세션을 갱신합니다.

---

## 4. 뷰티샵 관리자 미들웨어 및 온보딩 엔진 (Shop Admin Middleware Architecture)

신규 점주의 초기 진입(온보딩 모달)과 다매장 보유 원장의 실시간 매장 제어가 충돌하지 않도록 **2단계 미들웨어 파이프라인**을 구축합니다.

### (1) 라우트 진입점 설정 분리 (`bootstrap/app.php`)
`/shop-admin` 진입 그룹 전체에 매장 활성 검증(`shop_active`)을 걸어두면 매장이 아직 없는 신규 점주가 403 에러로 조기 차단되므로, 최상위 라우트에서는 이를 분리합니다.

* **최상위 라우트 그룹**: `middleware: ['web', 'auth', 'role:shop']`
* **서브 라우트 1 (대시보드 메인 및 등록 모달)**: `middleware: ['shop_onboarding']`
* **서브 라우트 2 (승인 매장 전용 관리 메뉴)**: `middleware: ['shop_active']`

### (2) 미들웨어 2중 게이트 작동 원리

#### 1. 활성 매장 세션 검증 가드 (`EnsureShopActive`)
* **보유 매장 0개 점주**: `$user->shops()->exists()`가 false인 경우 즉시 `$next($request)`로 통과시켜 온보딩 모달이 뜰 수 있도록 보장 (403 차단 방지).
* **보유 매장 1개 이상 점주**:
  * `session('active_shop_id')`가 없으면 승인 매장(`status: approved`) 또는 첫 번째 매장을 세션에 자동 바인딩.
  * 세션 매장이 본인 소유가 아닌 경우에만 엄격히 `abort(403)`.
  * 검증된 활성 샵 객체를 `view()->share('currentShop', $activeShop)`로 전역 공유.

#### 2. 온보딩 4단계 모달 가드 (`EnsureShopOnboarding`)
리다이렉트 대신 레이아웃(`shop_sidebar.blade.php`)과 연동되는 View Share 플래그를 주입하여 대시보드 화면 위에 모달을 띄웁니다.
* **관문 1 (휴대전화 인증)**: `!$user->is_phone_verified` ──▶ `view()->share('showPhoneVerifyModal', true)`
* **관문 2 (매장 등록)**: `!$user->shop` ──▶ `view()->share('showShopCreateModal', true)`
* **관문 3 (심사 대기)**: `$user->shop->status === 'pending'` ──▶ `view()->share('showShopPendingModal', true)`
* **관문 4 (심사 반려)**: `$user->shop->status === 'rejected'` ──▶ `view()->share('showShopRejectedModal', true)`

---

## 5. 통합 이미지 자산 아키텍처 (Global Image Subsystem)

플랫폼의 모든 이미지(프로필, 샵 대표 배너, 시술 프로그램 이미지, 마케팅 갤러리, 에디터 본문 이미지 등)는 중앙집중식 다형성(Polymorphic) 파이프라인으로 제어됩니다.

### (1) 테이블 스키마 (`images` & `imageables`)
* **`images` (단일 진실 공급원, Single Source of Truth)**
  * 파일 물리 정보: `disk`, `path`, `filename`, `original_name`, `size`
  * 파일 고유 해시(`hash`): `md5_file()` 기반 해시 인덱싱을 통한 물리적 중복 파일 업로드 방지 및 스토리지 재사용
  * 메타데이터: `title`, `alt_text`(SEO 최적화), `description`, `tags`(JSON)
* **`imageables` (다형성 다대다 연결 테이블, MorphToMany)**
  * `image_id`: `images.id` 외래키 (`cascadeOnDelete`)
  * `imageable_type`, `imageable_id`: 연관 모델(Shop, Program, Product 등) 다형성 바인딩
  * `sort_order`: 갤러리 및 상세 슬라이더 전시 우선순위 정렬

### (2) 이미지 서비스 파이프라인 (`App\Services\ImageService`)
1. **중복 검사 및 연계 (`storeAndAttach`)**:
   * 업로드 파일의 MD5 해시를 대조하여 중복 파일이면 물리 저장 없이 기존 `Image` 레코드를 재사용하고 대상 모델과 다형성 피벗 관계를 안전하게 바인딩합니다.
2. **에디터 선업로드 (`storeOnly`)**:
   * TinyMCE 등에서 글 작성 완료 전 비동기 선업로드되는 이미지를 단독 저장합니다.
3. **에디터 본문 동기화 (`syncEditorImages`)**:
   * HTML 본문(`description`)의 `<img>` 태그 URL을 정규식으로 파싱하여 매핑된 이미지 ID를 모델과 일괄 동기화(`sync`)합니다.
4. **참조 카운트 기반 안전 삭제 (Garbage Collection)**:
   * 이미지 분리(`detach`)나 수정 삭제 시 잔여 참조 수(`imageables` 레코드 카운트)가 `0`이 되면 스토리지 물리 파일 및 DB 레코드를 영구 삭제합니다.

---

## 6. 뷰티샵 핵심 아키텍처 및 스키마 (Shop Core Architecture & Schema)

뷰티샵(`shops`)은 플랫폼의 핵심 서비스 허브로서 **프로그램(시술) 티켓 사용처, 위치기반(LBS) 예약 거점, 제조사 제품 유통 대상** 역할을 담당합니다.

### (1) 핵심 데이터베이스 테이블 스키마 (`shops`)

| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 매장 고유 번호 |
| **2** | **FK** | `center_id` | Bigint (unsigned) | Y | null | 소속 뷰티센터 ID (`centers.id`, `nullOnDelete`) |
| **3** | **FK** | `user_id` | Bigint (unsigned) | Y | null | 매장 소유 원장 계정 (`users.id`, `nullOnDelete`) |
| **4** | - | `name` | String(255) | N | - | 뷰티샵 매장명 |
| **5** | - | `code` | String(50) | N | - | 매장 고유 식별 코드 (Unique) |
| **6** | - | `business_number` | String(15) | Y | null | 사업자등록번호 (Unique) |
| **7** | - | `ceo_name` | String(50) | N | - | 대표자 성명 |
| **8** | - | `ceo_phone` | String(30) | Y | null | 대표자 직통 연락처 (관리자 전용) |
| **9** | - | `ceo_email` | String(100) | Y | null | 대표자 이메일 (관리자 전용) |
| **10** | - | `tel` | String(20) | Y | null | 매장 대표 유선 전화번호 |
| **11** | - | `fax` | String(20) | Y | null | 매장 팩스 번호 |
| **12** | - | `zipcode` | String(10) | N | - | 우편번호 |
| **13** | - | `address1` | String(255) | N | - | 도로명 기본 주소 |
| **14** | - | `address2` | String(255) | Y | null | 상세 주소 |
| **15** | - | `region_sido` | String(50) | Y | null | 시/도 (인덱스) |
| **16** | - | `region_sigungu` | String(50) | Y | null | 시/군/구 (인덱스) |
| **17** | - | `region_category` | String(50) | Y | null | 권역 분류 색인 (인덱스) |
| **18** | - | `latitude` | Decimal(10,8) | Y | null | 위도 좌표 (인덱스) |
| **19** | - | `longitude` | Decimal(11,8) | Y | null | 경도 좌표 (인덱스) |
| **20** | - | `location_coords` | String(100) | Y | null | 공간 검색 보조 텍스트 좌표 |
| **21** | - | `is_reservation_enabled` | Boolean | N | `true` | 실시간 온라인 예약 허용 플래그 |
| **22** | - | `booking_unit_minutes` | Integer | N | 30 | 슬롯 예약 단위 (분 단위) |
| **23** | - | `business_start_time` | Time | Y | null | 영업 시작 시간 |
| **24** | - | `business_end_time` | Time | Y | null | 영업 종료 시간 |
| **25** | - | `break_start_time` | Time | Y | null | 휴게 시작 시간 |
| **26** | - | `break_end_time` | Time | Y | null | 휴게 마감 시간 |
| **27** | - | `holiday_configs` | Json | Y | null | 정기 휴무일 요일 설정 |
| **28** | - | `bank_name` | String(50) | Y | null | 정산 입금 은행명 |
| **29** | - | `bank_account` | String(50) | Y | null | 정산 계좌번호 |
| **30** | - | `bank_holder` | String(50) | Y | null | 정산 예금주명 |
| **31** | - | `logo_path` | String(255) | Y | null | 대표 썸네일 이미지 경로 |
| **32** | - | `banner_path` | String(255) | Y | null | PC 메인 배너 이미지 경로 |
| **33** | - | `og_image_path` | String(255) | Y | null | 소셜 공유용 OG 이미지 경로 |
| **34** | - | `kakao_channel_url` | String(255) | Y | null | 카카오톡 채널 URL |
| **35** | - | `naver_place_url` | String(255) | Y | null | 네이버 플레이스 URL |
| **36** | - | `youtube_id` | String(50) | Y | null | 유튜브 채널/영상 ID |
| **37** | - | `introduction` | Text | Y | null | 샵 한 줄 소개 문구 |
| **38** | - | `description` | LongText | Y | null | 상세 소개 본문 (에디터 HTML) |
| **39** | - | `is_verified` | Boolean | N | `false` | 사업자등록번호 국세청 진위인증 여부 |
| **40** | - | `is_phone_verified` | Boolean | N | `false` | 대표자 휴대폰 본인확인 여부 |
| **41** | - | `status` | String(20) | N | `'pending'` | 입점 심사: `pending`, `approved`, `rejected` |
| **42** | - | `operating_status` | String(20) | N | `'suspended'` | 영업 상태: `active`, `temp_closed`, `suspended`, `closed` |
| **43** | - | `rejection_reason` | Text | Y | null | 심사 반려 사유 |
| **44** | - | `is_pinned` | Boolean | N | `false` | 상단 고정 노출 여부 (최대 3개 제한) |
| **45** | - | `is_main_displayed` | Boolean | N | `false` | 메인 추천 샵 노출 여부 |
| **46** | - | `sort_order` | Integer | N | 0 | 전시 정렬 순서 |
| **47** | - | `admin_memo` | Text | Y | null | 본사 관리자 운영 메모 |
| **48** | - | `created_at` | Timestamp | Y | null | 생성 일시 |
| **49** | - | `updated_at` | Timestamp | Y | null | 수정 일시 |
| **50** | - | `deleted_at` | Timestamp | Y | null | 소프트 딜리트 삭제 일시 |

---

## 7. 시술 프로그램 및 패키지 아키텍처 (Program & Package Bundle Engine)

### (1) 3대 예약 실행 방식 (Execution Types)
회차(`session_count`)는 관리자나 소비자가 임의로 조작할 수 없으며, 상품의 유형에 따라 엄격히 규정됩니다.

* **1. 기본 프로그램 (`single`)**: 단일 케어 프로그램으로 회차는 **무조건 1회(`session_count = 1`)**로 고정.
* **2. 베이직 패키지 (`package_single`)**: 여러 프로그램을 묶어 당일 1회 방문에 연속 시술하는 결합 상품으로 **무조건 1회차(`session_count = 1`)**.
* **3. 스페셜 패키지 (`package_separate`)**: N회차 또는 복합 프로그램을 결합하여 **여러 번에 나누어 개별 예약·사용**하는 다회권. 회차는 번들 구성품 수량의 총합($\sum \text{quantity}$)으로 자동 계산.

### (2) 프로그램 데이터베이스 스키마 (`programs`)

| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 프로그램 고유 번호 |
| **2** | **FK** | `created_by` | Bigint (unsigned) | N | - | 등록자 ID (`users.id`) |
| **3** | - | `target_scope` | String(10) | N | `'ALL'` | 배포 범위: `ALL`(전체 샵), `CUSTOM`(일부 지정 샵) |
| **4** | - | `execution_type` | String(20) | N | `'single'` | 실행방식: `single`, `package_single`, `package_separate` |
| **5** | - | `code` | String(30) | N | - | 프로그램 고유 식별 코드 (Unique, 예: `P4913322`) |
| **6** | - | `name` | String(255) | N | - | 프로그램명 (인덱스) |
| **7** | - | `tags` | Json | Y | null | 검색 키워드 태그 배열 (JSON) |
| **8** | - | `logo_path` | String(255) | Y | null | 대표 썸네일 이미지 (600x600~1200x1200) |
| **9** | - | `banner_path` | String(255) | Y | null | PC 메인 배너 이미지 (1200x400) |
| **10** | - | `og_image_path` | String(255) | Y | null | 소셜 공유용 Open Graph 이미지 |
| **11** | - | `introduction` | Text | Y | null | 프로그램 한 줄 소개 |
| **12** | - | `description` | LongText | Y | null | 상세 안내 본문 (에디터 HTML) |
| **13** | - | `youtube_id` | String(50) | Y | null | 시술 안내 유튜브 영상 ID |
| **14** | - | `session_count` | UnsignedInteger | N | 1 | 총 이용 가능 회차 (1회권, N회권) |
| **15** | - | `duration_minutes` | UnsignedInteger | N | 60 | 1회 시술 기준 소요 시간(분) (슬롯 계산 기준) |
| **16** | - | `validity_days` | UnsignedInteger | N | 90 | 결제 후 티켓 사용 유효기간 (일 단위) |
| **17** | - | `original_price` | UnsignedInteger | N | 0 | 소비자가격 (정가) |
| **18** | - | `discount_price` | UnsignedInteger | N | 0 | 할인 금액 |
| **19** | - | `sale_price` | UnsignedInteger | N | 0 | 실제 판매 결제 가격 |
| **20** | - | `reward_points` | UnsignedInteger | N | 0 | 구매 시 적립 포인트 |
| **21** | - | `is_event_mall_item`| Boolean | N | `false` | 이벤트몰 전용 상품 여부 |
| **22** | - | `max_point_usage` | UnsignedInteger | Y | null | 결제 시 최대 사용 가능 포인트 한도 |
| **23** | - | `status` | String(20) | N | `'pending'` | 심사 상태: `draft`, `pending`, `approved`, `rejected` |
| **24** | - | `approved_at` | Timestamp | Y | null | 승인 일시 |
| **25** | - | `rejected_at` | Timestamp | Y | null | 반려 일시 |
| **26** | - | `rejection_reason` | Text | Y | null | 심사 반려 사유 |
| **27** | - | `is_pinned` | Boolean | N | `false` | 상단 고정 여부 |
| **28** | - | `pinned_at` | Timestamp | Y | null | 상단 고정 일시 |
| **29** | - | `is_main_displayed`| Boolean | N | `false` | 메인 추천 노출 여부 (인덱스) |
| **30** | - | `display_positions` | Json | Y | null | 메인 진영 플러그인 위치 배열 (예: `["A", "C"]`) |
| **31** | - | `sort_order` | Integer | N | 0 | 정렬 순서 |
| **32** | - | `admin_memo` | Text | Y | null | 본사 관리자 운영 메모 |
| **33** | - | `is_active` | Boolean | N | `true` | 판매 활성화 여부 (인덱스) |
| **34** | - | `sales_start_at` | Timestamp | Y | null | 특가/기획전 판매 시작일 |
| **35** | - | `sales_end_at` | Timestamp | Y | null | 특가/기획전 판매 마감일 |
| **36** | - | `max_sales_limit` | UnsignedInteger | Y | null | 선착순 한정 판매 인원수 |
| **37** | - | `view_count` | UnsignedInteger | N | 0 | 조회수 캐시 카운터 |
| **38** | - | `sales_count` | UnsignedInteger | N | 0 | 누적 결제 판매수량 캐시 카운터 |
| **39** | - | `review_count` | UnsignedInteger | N | 0 | 후기 등록수 캐시 카운터 |
| **40** | - | `qna_count` | UnsignedInteger | N | 0 | 문의 등록수 캐시 카운터 |
| **41** | - | `created_at` | Timestamp | Y | null | 생성 일시 |
| **42** | - | `updated_at` | Timestamp | Y | null | 수정 일시 |
| **43** | - | `deleted_at` | Timestamp | Y | null | 소프트 딜리트 삭제 일시 |

### (3) 패키지 번들 구성 테이블 (`program_bundles`)
* `parent_program_id`: 패키지 부모 프로그램 ID (`foreignId`, `cascadeOnDelete`)
* `child_program_id`: 단위 기본 프로그램 ID (`foreignId`, `cascadeOnDelete`)
* `quantity`: 포함 제공 횟수 (기본값 1)
* `sort_order`: 패키지 상세 안내 노출 순서
* **무결성 제약**: `['parent_program_id', 'child_program_id']` 복합 Unique 인덱스 적용

### (4) 본사 배포 범위 및 뷰티샵 매핑 (`shop_program`)
* **배포 범위 (`programs.target_scope`)**: `ALL` (전국 매장 일괄 배포), `CUSTOM` (본사 지정 샵 매핑)
* **뷰티샵별 개별 통제 피벗 (`shop_program`)**:
  * `is_active`: 원장 제어 플래그 (배정된 프로그램을 매장 상황에 따라 On/Off)
  * `min_advance_hours`: 최소 사전 예약 여유 시간 (기본 72시간 전)
  * `cancel_deadline_hours`: 고객 무료 취소 마감 기한 (기본 72시간 전)
  * `after_deadline_policy`: 기한 경과 후 당일 노쇼/취소 시 패널티 규약 (`counted_as_used`: 티켓 차감 처리)
  * `sort_order`: 매장 내부 노출 순서

---

## 8. 주문, 결제 및 모바일 티켓 발급 엔진 (Order, Payment & Ticket Pipeline)

### (1) 사용자 쇼핑 및 주문/결제 퍼널 (Customer Funnel)
1. **프로그램 탐색 (`/programs`)**: 본사 승인(`approved`) 및 판매 활성화(`is_active=true`)된 프로그램 목록 렌더링
2. **프로그램 상세 (`/programs/{code}`)**: 자동 발급된 고유 프로그램 코드(예: `P4913322`) 기반 상세 정보 조회
3. **주문서 작성 (`/orders/checkout?type=program&code={code}&quantity={qty}`)**:
   * 소셜 가입자 정보 자동 인입 및 필수 휴대폰 본인인증 (`phone`, `is_phone_verified=true`)
   * **필수 약관 가드**: 서비스 이용약관 전문 열람 확인 + 취소 및 환불 규정 전문 열람 확인 필수
   * **결제 수단 분기**: 신용카드(`card`, 즉시 결제 완료) vs 무통장 입금(`transfer`, 7일 입금 마감 기한 `deposit_due_at` 부여)
4. **결제 완료 안내 (`/orders/complete/{order_id}`)**: 주문 완료 및 무통장 입금 계좌/기한 안내 출력
5. **마이페이지 주문 내역 (`/mypage/orders`, `/mypage/orders/{order_id}`)**: 주문 리스트 및 입금 대기 상태 안내 상시 노출

### (2) 주문 및 결제 핵심 데이터베이스 스키마

#### 주문 마스터 (`orders`)
| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 주문 식별 고유 번호 |
| **2** | **FK** | `user_id` | Bigint (unsigned) | N | - | 주문 회원 ID (`users.id`, `cascadeOnDelete`) |
| **3** | - | `order_code` | String(50) | N | - | 주문 고유 식별 번호 (Unique, 예: `GB-20260906-001234`) |
| **4** | - | `total_amount` | Decimal(12,2) | N | - | 총 실결제 금액 |
| **5** | - | `status` | String(20) | N | `'pending'` | `pending`(대기), `paid`(완료), `cancelled`(취소), `expired`(만료), `refunded`(환불) |
| **6** | - | `payment_method` | String(20) | N | `'card'` | `card`, `transfer`, `virtual_account`, `mobile` |
| **7** | - | `paid_at` | Timestamp | Y | null | 결제 완료 일시 |
| **8** | - | `pg_transaction_key` | String(100) | Y | null | PG사 고유 승인 키 (토스 paymentKey 등) |
| **9** | - | `pg_auth_number` | String(50) | Y | null | PG사/카드사 승인 번호 또는 영수증 식별값 |
| **10** | - | `depositor_name` | String(50) | Y | null | 무통장 입금자 성명 |
| **11** | - | `deposit_due_at` | Timestamp | Y | null | 입금 마감 기한 (생성 시점 + 7일) |
| **12** | - | `virtual_account_number` | String(50) | Y | null | 가상계좌 번호 |
| **13** | - | `virtual_account_bank` | String(30) | Y | null | 가상계좌 은행명 |
| **14** | - | `orderer_name` | String(50) | N | - | 주문자 성함 |
| **15** | - | `orderer_phone` | String(30) | N | - | 주문자 연락처 (하이픈 제외, 모바일 알림 수신용) |
| **16** | - | `orderer_email` | String(100) | N | - | 주문자 이메일 (모바일 티켓 전송용) |
| **17** | - | `receiver_name` | String(50) | Y | null | 수령인 이름 (커머스 제품 배송용) |
| **18** | - | `receiver_phone` | String(30) | Y | null | 수령인 연락처 (커머스 제품 배송용) |
| **19** | - | `zipcode` | String(20) | Y | null | 배송 우편번호 |
| **20** | - | `address1` | String(255) | Y | null | 배송 기본 주소 |
| **21** | - | `address2` | String(255) | Y | null | 배송 상세 주소 |
| **22** | - | `delivery_memo` | Text | Y | null | 배송 요청사항 |
| **23** | - | `delivery_status` | String(20) | N | `'ready'` | 배송 상태: `ready`(준비중), `shipping`(배송중), `delivered`(완료) |
| **24** | - | `tracking_number` | String(50) | Y | null | 택배 운송장 번호 |
| **25** | - | `carrier_name` | String(50) | Y | null | 택배사 명칭 |
| **26** | - | `shipped_at` | Timestamp | Y | null | 상품 발송 일시 |
| **27** | - | `delivered_at` | Timestamp | Y | null | 배송 완료 일시 |
| **28** | - | `cancelled_at` | Timestamp | Y | null | 주문 취소 일시 |
| **29** | - | `refunded_at` | Timestamp | Y | null | 환불 완료 일시 |
| **30** | - | `created_at` | Timestamp | Y | null | 주문 생성 일시 |
| **31** | - | `updated_at` | Timestamp | Y | null | 주문 수정 일시 |
| **32** | - | `deleted_at` | Timestamp | Y | null | 소프트 딜리트 삭제 일시 |

#### 주문 상품 상세 (`order_items`)
| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 주문 상세 고유 번호 |
| **2** | **FK** | `order_id` | Bigint (unsigned) | N | - | 주문 마스터 ID (`orders.id`, `cascadeOnDelete`) |
| **3** | - | `item_type` | String(255) | N | - | 다형성 모델 (`Program::class`, `Product::class`) |
| **4** | - | `item_id` | Bigint (unsigned) | N | - | 다형성 상품 ID |
| **5** | - | `item_name` | String(255) | N | - | 구매 당시 상품명 스냅샷 보존 |
| **6** | - | `quantity` | Integer | N | 1 | 구매 수량 |
| **7** | - | `unit_price` | Decimal(12,2) | N | - | 구매 당시 기본 정가(단가) |
| **8** | - | `total_option_price` | Decimal(12,2) | N | 0.00 | 선택된 옵션 추가 금액 총합 |
| **9** | - | `item_discount_amount`| Decimal(12,2) | N | 0.00 | 상품 자체 프로모션 할인 총액 |
| **10** | - | `coupon_discount_amount`| Decimal(12,2)| N | 0.00 | 쿠폰 할인 총액 |
| **11** | - | `sale_price` | Decimal(12,2) | N | - | 실 판매 단가 |
| **12** | - | `total_price` | Decimal(12,2) | N | - | 최종 결제 금액 (`sale_price * quantity`) |
| **13** | **FK** | `user_coupon_id` | Bigint (unsigned) | Y | null | 사용된 회원 쿠폰 ID (`user_coupons.id`, `nullOnDelete`) |
| **14** | - | `created_at` | Timestamp | Y | null | 생성 일시 |
| **15** | - | `updated_at` | Timestamp | Y | null | 수정 일시 |

#### 주문 상품 옵션 (`order_item_options`)
* `order_item_id`: 주문 상세 외래키 (`cascadeOnDelete`)
* `product_option_id`: 원본 옵션 마스터 ID (`nullOnDelete`)
* `option_name`, `quantity`, `additional_price`, `total_additional_price`: 구매 당시 옵션명 및 추가금액 스냅샷

### (3) 디지털 티켓 보관함 및 회차 아이템 발급 엔진

#### 티켓 마스터 (`user_tickets`)
| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 티켓 마스터 고유 번호 |
| **2** | **FK** | `user_id` | Bigint (unsigned) | N | - | 소유 회원 ID (`users.id`, `cascadeOnDelete`) |
| **3** | **FK** | `order_item_id` | Bigint (unsigned) | N | - | 구매 주문 상세 ID (`order_items.id`, `cascadeOnDelete`) |
| **4** | - | `ticket_code` | String(30) | N | - | 모바일 바코드/QR용 16자리 고유 번호 (Unique, 예: `T123456789012345`) |
| **5** | - | `status` | String(20) | N | `'active'` | 상태: `active`(사용가능), `exhausted`(소진), `expired`(만료), `cancelled`(환불) |
| **6** | - | `expires_at` | Date | Y | null | 티켓 전체 유효 만료일 (`now() + validity_days`) |
| **7** | - | `created_at` | Timestamp | Y | null | 발급 일시 |
| **8** | - | `updated_at` | Timestamp | Y | null | 수정 일시 |

#### 티켓 아이템 상세 (`user_ticket_items`) - 실제 예약 슬롯 및 1회 차감 단위
| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 회차 아이템 고유 번호 |
| **2** | **FK** | `user_ticket_id` | Bigint (unsigned) | N | - | 부모 티켓 마스터 ID (`user_tickets.id`, `cascadeOnDelete`) |
| **3** | **FK** | `program_id` | Bigint (unsigned) | Y | null | 원본 시술 프로그램 ID (`programs.id`, `nullOnDelete`) |
| **4** | - | `program_name` | String(255) | N | - | 구매 당시 프로그램명 스냅샷 보존 |
| **5** | - | `total_count` | Integer | N | 1 | 총 부여 회차 (기본 1회, 번들 수량) |
| **6** | - | `remaining_count` | Integer | N | 1 | 잔여 이용 가능 회차 (시술 완료 시 1회씩 차감) |
| **7** | - | `status` | String(20) | N | `'active'` | 아이템 상태: `active`(잔여회차 있음), `exhausted`(소진완료) |
| **8** | - | `created_at` | Timestamp | Y | null | 생성 일시 |
| **9** | - | `updated_at` | Timestamp | Y | null | 수정 일시 |

---

## 9. 위치기반(LBS) 엔진 및 모바일 QR 티켓 시스템 (LBS & Mobile QR Engine)

### (1) 위치기반 뷰티샵 탐색 파이프라인 (LBS Pipeline)
* **주소 정제 및 좌표 자동 연동**: Daum 우편번호 서비스 + 카카오 지도 Geocoder API로 위경도(`latitude`, `longitude`) 자동 추출 및 DB 인덱싱 저장
* **고객 맞춤 샵 탐색 (PC / Mobile)**:
  * 브라우저 HTML5 Geolocation API(`navigator.geolocation`) 기반 현재 접속자 좌표 수집
  * 하버사인 공식(Haversine Formula)을 활용한 직선거리(km) 기준 가까운 매장 정렬 렌더링 및 카카오 지도 마커/오버레이 표출

### (2) 모바일 티켓/예약권 QR 코드 엔진 (Mobile QR Code Engine)
* **고유 식별 코드 발급**: 티켓 마스터 16자리 `ticket_code`, 예약권 고유 `reservation_code` 자동 매핑
* **동적 QR 코드 렌더링**: 고객 마이페이지 진입 시 실시간 QR 이미지 생성 (별도 앱 설치 없는 모바일 웹 뷰어)

### (3) 뷰티샵 관리자 UI/UX 철학 및 현장 실행 파이프라인 (Shop Admin UX)
* **최고관리자 vs 뷰티샵관리자 환경 분리**:
  * **최고관리자 (`/admin`)**: 대량 데이터 통제, 심사, 배포, 정산 중심의 **PC 중심 UI**
  * **뷰티샵 원장 (`/shop-admin`)**: 시술 현장에서 스마트폰으로 모든 조작을 완결할 수 있는 **모바일 최적화 UI**
* **원장의 4단계 간편 현장 동선**:
  1. **신규 예약 대기 알림 확인**: 모바일 상단 뱃지/대시보드에서 `pending` 건 즉시 파악
  2. **상담 및 예약 확정**: 고객 요구사항 조율 후 [예약 확정(`confirmed`)] 원클릭 토글
  3. **내방 고객 QR 스캔**: 원장 모바일 카메라로 고객 QR 코드 리딩 (또는 4자리 수동 조회)
  4. **티켓 회차 소진 완료**: 시술 완료 시 [사용 확인] 클릭 $\rightarrow$ `user_ticket_items.remaining_count` 1회 즉시 차감 및 완료(`completed`) 종결

---

## 10. 뷰티샵 방문 예약 및 현장 QR 스캔 차감 파이프라인 (Reservation & QR Verification)

### (1) 고객 예약 퍼널 (`/mypage/reservations/create/{ticket_item_id}`)
1. **티켓 선택 및 진입**: 디지털 티켓 보관함에서 원하는 티켓 아이템의 [예약하기] 클릭
2. **희망 일자 선택**: 달력에서 방문 희망 일자 선택
3. **시술 가능 뷰티샵 탐색 및 LBS 지도 렌더링**: 배정 샵(`shop_program.is_active=true`) 필터링 및 카카오 지도 핀 노출
4. **방문 슬롯 선택**: 영업시간, 예약 단위(`booking_unit_minutes`), 휴게시간 및 기예약 슬롯을 제외한 잔여 슬롯 선택
5. **예약 신청 및 모달 확인**: 최종 확인 시 `reservations` 테이블에 **예약 대기(`status: pending`)** 등록

### (2) 뷰티샵 관리자 승인 및 현장 QR 인증 차감 엔진
1. **원장 예약 접수 및 확정 (`/shop-admin/reservations`)**: 원장 확인 후 `status: confirmed` 전환
2. **티켓/예약 상세 및 모바일 QR 렌더링**: 마이페이지 예약 상세 화면에 고유 QR 코드 표출
3. **원장 현장 모바일 QR 스캔 (소유권 검증 및 회차 차감)**:
   * 원장이 모바일 카메라로 고객 QR 코드 스캔
   * 티켓 소유권(`user_id`), 예약 매장 일치 여부, 잔여 회차(`remaining_count > 0`) 즉각 검증
   * [시술 완료 및 차감] 승인 시:
     * 예약 상태를 `completed`로 갱신
     * `user_ticket_items.remaining_count` 1회 차감 (0 도달 시 `exhausted` 전환)
     * 하위 모든 아이템 소진 시 부모 `user_tickets` 상태 `exhausted` 종결

* **예약 차감 상태 전이 흐름**
  * [고객 예약 신청] ──▶ status: pending
  * [원장 확인 및 승인] ──▶ status: confirmed
  * [고객 매장 내방 후 원장 모바일 QR 스캔] ──▶ 소유권 및 매장 검증
  * [원장 시술 완료 처리] ──▶ status: completed
  * [티켓 회차 차감] ──▶ user_ticket_items.remaining_count 1회 차감 (0 도달 시 status: exhausted 전환)

---

## 11. 메시징 및 알림 서브시스템 (Messaging & Notification Subsystem)

고객 본인인증, 주문/결제 확인, 뷰티샵 예약 접수 및 확정 등 모든 플랫폼 통보는 중앙 집중형 알림 템플릿과 발송 서비스를 통해 제어되며, 발송 결과는 감사 추적 로그로 보존됩니다.

### (1) 알림 및 감사 로깅 데이터베이스 7단 표준 스키마

#### 1. 알림 템플릿 마스터 (`notification_templates`)
| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 템플릿 고유 식별 번호 |
| **2** | - | `code` | String(50) | N | - | 템플릿 고유 식별 코드 (Unique, 영문 대문자/숫자/언더바) |
| **3** | - | `category` | String(30) | N | - | 2단 대분류: `auth`(인증), `reservation`(예약), `billing`(결제/정산), `notice`(공지) |
| **4** | - | `subcategory` | String(30) | N | - | 2단 소분류: `verify`, `password`, `apply`, `confirm`, `cancel`, `remind`, `payment` |
| **5** | - | `target` | String(20) | N | `'user'` | 수신 대상: `user`(일반회원), `shop`(뷰티샵), `admin`(최고관리자) |
| **6** | - | `type` | String(20) | N | `'SMS'` | 전송 채널: `SMS`, `LMS`, `ALIMTALK`, `EMAIL` |
| **7** | - | `title` | String(255) | Y | null | LMS 제목 / 알림톡 강조 타이틀 / 이메일 발송 제목 |
| **8** | - | `content` | Text | N | - | 발송 본문 문구 (치환 변수 `#{name}`, `#{code}` 등 포함) |
| **9** | - | `template_key` | String(100) | Y | null | 카카오 비즈니스 사전 심사 승인 템플릿 키 |
| **10** | - | `description` | Text | Y | null | 관리자용 메모 및 치환 가능 변수 가이드 |
| **11** | - | `is_active` | Boolean | N | `true` | 발송 활성화 여부 (ON/OFF 원클릭 스위치) |
| **12** | - | `created_at` | Timestamp | Y | null | 생성 일시 |
| **13** | - | `updated_at` | Timestamp | Y | null | 수정 일시 |
| **14** | - | `deleted_at` | Timestamp | Y | null | 소프트 딜리트 삭제 일시 (휴지통 보존) |

#### 2. SMS/LMS 발송 감사 이력 (`sms_logs`)
| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | SMS 발송 로그 고유 식별자 |
| **2** | **FK** | `user_id` | Bigint (unsigned) | Y | null | 수신 회원 ID (`users.id`, 비회원 발송 시 null) |
| **3** | - | `phone` | String(20) | N | - | 수신 휴대폰 번호 (하이픈 제외 숫자, 복합 인덱스) |
| **4** | - | `message` | Text | N | - | 실제 발송된 문자 전체 본문 내용 |
| **5** | - | `type` | String(30) | N | `'verify'` | 발송 구분 (`phone_verification`, `RESERVE_APPLY` 등, 복합 인덱스) |
| **6** | - | `status` | String(20) | N | `'success'` | 발송 상태: `success`(성공), `failed`(실패) |
| **7** | - | `error_message` | Text | Y | null | 발송 실패 시 Cafe24 게이트웨이 응답 전문 또는 예외 사유 |
| **8** | - | `ip_address` | String(45) | Y | null | 발송 요청 클라이언트 IP 주소 |
| **9** | - | `created_at` | Timestamp | Y | null | 발송 일시 (인덱스) |
| **10** | - | `updated_at` | Timestamp | Y | null | 상태 갱신 일시 |

#### 3. 이메일 발송 감사 이력 (`email_logs`)
| 번호 | PK/FK | 컬럼명 | 타입 | Null | 기본값 | 설명 및 상태값 |
| :---: | :---: | :--- | :--- | :---: | :---: | :--- |
| **1** | **PK** | `id` | Bigint (unsigned) | N | Auto | 로그 고유 식별 번호 |
| **2** | **FK** | `user_id` | Bigint (unsigned) | Y | null | 수신 회원 ID (`users.id`, 비회원 시 null, 인덱스) |
| **3** | - | `recipient_email` | String(255) | N | - | 수신자 이메일 주소 (인덱스) |
| **4** | - | `subject` | String(255) | N | - | 발송된 메일 제목 |
| **5** | - | `content` | LongText | N | - | 실제 발송된 메일 본문 (HTML/Text 전문) |
| **6** | - | `type` | String(50) | N | `'NOTICE'` | 발송 목적 (`RESERVE_NOTIFY`, `SHOP_APPLY`, `NOTICE` 등) |
| **7** | - | `status` | String(20) | N | `'PENDING'` | 전송 상태: `PENDING`(대기), `SUCCESS`(성공), `FAIL`(실패) |
| **8** | - | `error_message` | Text | Y | null | 발송 실패 시 SMTP / 메일러 에러 메시지 |
| **9** | - | `created_at` | Timestamp | Y | null | 발송 요청 일시 |
| **10** | - | `updated_at` | Timestamp | Y | null | 상태 업데이트 일시 |

### (2) 카페24 SMS 서비스 파이프라인 (`App\Services\Cafe24SmsService`)
* **본인인증 전용 발송 (`sendVerificationCode`)**: 6자리 난수 인증코드를 고정 포맷으로 발송하고 결과를 `sms_logs`에 영구 보존.
* **90바이트 자동 분기 엔진 (`send`)**:
  * `iconv('UTF-8', 'EUC-KR//IGNORE', $message)`로 바이트 계산.
  * **90바이트 이하**: 단문(`smsType = 'S'`) 모드로 발송.
  * **90바이트 초과**: 자동으로 장문(`smsType = 'L'`) 및 타이틀(`title`) 모드로 전환 발송하여 한글 깨짐 방지.
  * 발송 결과(성공/실패, 에러 본문, 요청자 IP)를 `sms_logs`에 영구 기록.

### (3) 알림 템플릿 통합 서비스 (`App\Services\NotificationService`)
* **치환 변수 파싱 엔진**:
  * `NotificationTemplate` 모델의 `parseTitle($variables)`, `parseContent($variables)` 메서드를 통해 본문 내 `#{key}` 문자열을 동적 값으로 치환.
* **무중단 Fallback 전송 전략**:
  * 카카오 알림톡(`ALIMTALK`) 심사 전 단계이므로, 채널 설정이 `ALIMTALK`, `SMS`, `LMS`인 경우 모두 `Cafe24SmsService`를 통해 단문/장문 문자로 일괄 우회 발송.
  * `EMAIL` 타입은 향후 메일러 연동 슬롯으로 격리.
* **원클릭 발송 제어**:
  * 관리자가 특정 템플릿의 `is_active`를 OFF로 전환하면 해당 알림 발송이 즉각 일시 중지.

---

## 12. 마이페이지 회원정보 수정 파이프라인 (Mypage Profile Management Engine)

소셜 가입자와 일반 가입자를 모두 수용하며, 전화번호 변경 시 동적 SMS 본인인증 재검증, Daum 우편번호 인라인 레이어 검색, Flatpickr 한국어 생년월일 피커, 모카/브론즈 테마(`--primary-color: #b18977;`, 고정 둥글기 `6px`)를 적용한 마이페이지 핵심 서브시스템입니다.

* **마이페이지 회원정보 수정 프로세스 다이어그램**
  * [사용자 진입] ──▶ GET /mypage/profile
  * [컨트롤러 처리 (`ProfileController@edit`)] ──▶ `$isSocialUser = $user->socialAccounts()->exists();` ──▶ `view('mypage.profile.edit', compact('user', 'isSocialUser'))`
  * [Blade 뷰 렌더링 (`layouts.app-sub` 계승)]
    * 1. 기본 프로필 폼 (PATCH `/mypage/profile` ──▶ `ProfileController@update`)
      * 이메일: 읽기 전용 (계정 고유 식별키)
      * 이름 / 성별: 2열 그리드 (`col-12 col-md-6`)
      * 휴대전화번호: `sms_auth.js` 연동 (번호 수정 시 실시간 감지 및 인증 플래그 리셋)
      * 생년월일: Flatpickr 한국어 달력 연동 (`col-12 col-md-6`)
      * 배송지 주소: Daum 우편번호 인라인 레이어 (`#wrap`, 팝업 차단 원천 방지)
      * 마케팅 동의: SMS/알림톡 및 이메일 수신 동의 체크박스
    * 2. 비밀번호 폼 (PUT `/mypage/profile/password` ──▶ `ProfileController@updatePassword`)
      * 일반 회원: [현재 비밀번호 확인] 필수 ──▶ [새 비밀번호 8자 이상]
      * 소셜 회원: [현재 비밀번호 면제] ──▶ [자체 로그인용 비밀번호 신규 등록]

### (1) 라우팅 아키텍처 (`routes/web.php`)
기존 라라벨 Breeze의 독립 `/profile` 라우트(`routes/auth.php`)와의 충돌을 제거하고, 마이페이지 라우트 그룹 하위(`/mypage/profile`)로 완전 일원화합니다.

* `Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');` (회원정보 수정 화면 조회)
* `Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');` (기본 개인정보 및 마케팅 수신동의 수정)
* `Route::put('/profile/password', [ProfileController::class, 'updatePassword'])->name('profile.password.update');` (비밀번호 신규 등록 및 변경)
* `Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');` (회원 탈퇴 처리, SoftDeletes)

### (2) 컨트롤러 비즈니스 로직 (`App\Http\Controllers\ProfileController`)
* **`edit(Request $request)`**:
  * `$isSocialUser = $user->socialAccounts()->exists();`를 판별하여 뷰에 전달.
  * 소셜 연동 여부에 따라 비밀번호 카드 UI 및 검증 규칙 분기.
* **`update(Request $request)`**:
  * **전화번호 하이픈 정제**: `$request->filled('phone')`인 경우 정규식(`preg_replace('/[^0-9]/', '', ...)`)으로 숫자만 추출하여 `request->merge()` 처리.
  * **대량 할당(Mass Assignment) 가드**: `User::$fillable`에 명시된 컬럼(`name`, `gender`, `birth_date`, `phone`, `zipcode`, `address1`, `address2`, `agrees_to_sms`, `agrees_to_email`)만 엄격히 유효성 검사.
  * **전화번호 변경 감지 및 본인인증 강제**: 기존 등록 번호와 입력된 번호가 다를 경우 `is_phone_verified === 'true'` 여부를 서버에서 재검증. 미인증 시 에러 백(`withErrors`) 반환.
  * **불리언 플래그 처리**: 체크박스 미체크 시 null로 넘어오는 값을 `$request->boolean('agrees_to_sms')`, `$request->boolean('agrees_to_email')`로 확정 변환.
* **`updatePassword(Request $request)`**:
  * 일반 회원: `current_password` 규칙을 적용하여 기존 비밀번호 일치 여부 확인.
  * 소셜 회원: 소셜 간편인증으로 본인확인이 이미 완료된 상태이므로 `current_password` 검증을 면제하고 즉시 새 비밀번호(`Password::defaults()`) 해싱 등록.

### (3) 프론트엔드 UI/UX 사양 (`resources/views/mypage/profile/edit.blade.php`)
1. **레이아웃 및 상단 브레드크럼 통합**:
   * 마스터 레이아웃 `layouts.app-sub`의 `@hasSection('header-title')`과 연동되어 대제목(H1) 및 PC 브레드크럼(마이페이지 > 회원정보 수정)이 상단에 자동 표출되며, 본문 내부의 중복 타이틀 바는 완전히 배제.
2. **2열 반응형 그리드 배치 (`row g-3`)**:
   * 이름 & 성별: `<div class="col-12 col-md-6 mb-3">`
   * 휴대전화번호 & 생년월일: `<div class="col-12 col-md-6 mb-3">` (동일한 2열 그리드로 정돈하여 시각적 답답함 해소)
3. **Flatpickr 한국어 생년월일 피커**:
   * 브라우저 기본 date 피커를 차단하고 Flatpickr 한국어 로케일(`l10n/ko.js`)을 바인딩.
   * `maxDate: "today"`, `monthSelectorType: "dropdown"` 옵션을 부여하고 브론즈 테마(`.flatpickr-day.selected`)로 스타일링.
4. **Daum 우편번호 인라인 레이어 (`#wrap`)**:
   * 모바일 팝업 차단 문제를 원천 방지하기 위해 팝업(`open()`) 대신 인풋 하단에 `320px` 높이의 인라인 컨테이너(`.wrap-box`)를 토글 렌더링(`embed()`).
   * 검색 완료 시 상세주소(`address2`)로 포커스를 자동 인계하고 레이어를 자동 닫힘 처리.
5. **풍부한 안내 가이드 시스템**:
   * 상단 개인정보 암호화 보호 안내 배너 완비.
   * 이메일(식별키 변경 불가 안내), 실명(예약/수령인 대조), 성별(맞춤 케어 추천), 휴대전화(티켓 바코드 수신처), 생년월일(생일 쿠폰 발급), 배송지 주소(택배 기준지), 마케팅 수신동의(법적 필수 공지 분기 안내) 등 필드별 헬프 텍스트 제공.

### (4) 공용 SMS 모듈 실시간 번호 추적 메커니즘 (`public/js/sms_auth.js`)
* **초기 상태 스냅샷**: 페이지 로드 시 기존 인증 완료 번호(`initialVerifiedPhone`)를 메모리에 보관.
* **실시간 입력 감지 (`input` 이벤트 위임)**:
  * 사용자가 번호를 1자리라도 수정하면 `[data-sms-role="verified-flag"]` 값을 `'false'`로 즉시 리셋, '인증완료' 뱃지 은닉, 전송 버튼 텍스트를 `'새 번호 인증'`으로 동적 전환.
  * 오입력 후 다시 원래 인증 번호로 되돌리면 즉시 `verified-flag`를 `'true'`로 복원하고 뱃지 재노출.
* **[번호 수정] 버튼 상호작용 (`edit-btn`)**:
  * 인증 완료 상태에서는 인풋이 `readOnly` 및 `bg-light`로 잠금 처리되고 `[번호 수정]` 버튼이 노출.
  * 버튼 클릭 시 인풋 잠금 해제, 배경색 복구, 포커스 인계 및 인증 입력창 초기화 수행.

---

## 13. 데이터 흐름 및 온보딩 파이프라인 (Data Flow & Onboarding)

### (1) 뷰티샵 입점 신청 트랜잭션 (`ShopController@store`)
1. **사전 검증**: 국세청 사업자등록번호 진위확인(`is_verified`) 및 신청인 휴대폰 본인인증(`is_phone_verified`) 완료 검증
2. **트랜잭션 진입 (`DB::beginTransaction`)**:
   * **회원 (User)**: `phone` 저장 및 `is_phone_verified=true` 갱신
   * **뷰티샵 (Shop)**: 필수 정보 등록 (`status='pending'`, `operating_status='suspended'`)
     * 관리자 전용 필드(`tel`, `ceo_phone`, `ceo_email`, `fax`)는 초기 생성 시 `null` 유지
   * **세션 (Session)**: 신규 매장 ID를 `active_shop_id` 세션에 등록
3. **트랜잭션 커밋**: SMS 세션 폐기 후 대기 안내 페이지 이동

### (2) 뷰티샵 등록 및 온보딩 3대 파이프라인 (Shop Onboarding Paths)
* **경로 A: 사용자 직접 입점 신청 (`/shops/create`)**: 일반 사용자가 신청 ──▶ 본사 심사 ──▶ 승인 시 Role `shop` 동기화
* **경로 B: 본사 권한 선부여 후 점진적 온보딩 (Role-First)**: 최고관리자가 `shop` 역할 부여 ──▶ `/shop-admin` 첫 진입 ──▶ `EnsureShopActive` 통과 ──▶ `EnsureShopOnboarding` 4단계 모달 가드(폰인증 ──▶ 샵등록 ──▶ 대기 ──▶ 반려) 순차 통과
* **경로 C: 최고관리자 직접 등록 및 통합 관리 (Admin Provisioning)**: 최고관리자가 `/admin/shops`에서 직접 생성, 소유자 바인딩 및 즉시 승인(`status: approved`, `operating_status: active`)

---

## 14. 핵심 디렉토리 및 클래스 맵 (Key Directory & Class Map)

* **Models**:
  * `App\Models\User`: 회원 계정 및 프로필 마스터 (`fillable`: `gender`, `birth_date`, `zipcode`, `address1`, `address2`, `agrees_to_sms` 등 일체 포함, `casts`: `birth_date => date`, `agrees_to_* => boolean`)
  * `App\Models\SocialAccount`: 다중 SNS 매핑 모델 (`belongsTo(User::class)`, `['provider', 'provider_id']` Unique)
  * `App\Models\Shop`: 뷰티샵 매장 정보, LBS 좌표, 영업/예약 시간 정책, 카테고리/이미지/프로그램 다대다 연동
  * `App\Models\Program`: 시술 상품 정보, 3대 실행 방식, 가격 및 회차 규약
  * `App\Models\ProgramBundle`: 패키지 프로그램(부모)과 구성요소(자식) 간의 수량 매핑 피벗 모델
  * `App\Models\Order`: 주문 마스터 모델 (결제 수단, 결제 상태, PG 키, 무통장 입금 정보)
  * `App\Models\OrderItem`: 다형성 주문 상세 레코드 및 단가 스냅샷
  * `App\Models\OrderItemOption`: 주문 당시 선택된 옵션 및 추가금 스냅샷
  * `App\Models\UserTicket`: 디지털 모바일 바우처 티켓 마스터 (16자리 `ticket_code`, 유효기간)
  * `App\Models\UserTicketItem`: 실제 예약 및 1회씩 차감되는 단위 회차 슬롯 모델
  * `App\Models\Reservation`: 뷰티샵 방문 예약 내역, 예약 일시, 상태 전이 모델
  * `App\Models\Image`: 중앙집중식 미디어 파일 정보 및 MD5 해시 무결성 관리
  * `App\Models\NotificationTemplate`: 알림 템플릿 마스터, 2단 분류 상수, 스코프 및 치환 파서(`parseContent`, `parseTitle`)
* **Services**:
  * `App\Services\ImageService`: MD5 해시 기반 중복 방지 저장(`storeOnly`), 모델 다형성 안전 바인딩(`storeAndAttach`)
  * `App\Services\OrderService`: 비관적 락(`lockForUpdate`) 기반 주문 생성, 결제 수단별 처리, 모바일 티켓 및 슬롯 자동 발급
  * `App\Services\Cafe24SmsService`: 90바이트 SMS/LMS 자동 분기, 인증번호 발송, 발송 로그(`sms_logs`) 감사 추적
  * `App\Services\NotificationService`: 템플릿 기반 동적 알림 발송 및 알림톡-SMS Fallback 처리
* **Controllers & Middleware**:
  * `App\Http\Controllers\ProfileController`: **마이페이지 회원정보 수정(`edit`, `update`), 소셜 분기 비밀번호 관리(`updatePassword`), 계정 탈퇴(`destroy`)**
  * `App\Http\Controllers\Auth\SocialLoginController`: 3대 소셜 로그인 리다이렉트 및 콜백 동시성 계정 통합 제어
  * `App\Http\Controllers\Auth\VerifyPhoneController`: 휴대폰 SMS 인증번호 발송(`/auth/phone/send`) 및 6자리 검증(`/auth/phone/verify`)
  * `App\Http\Controllers\ShopController`: 사용자 입점 신청 및 프론트엔드 매장 생성 파이프라인
  * `App\Http\Controllers\Admin\ShopController`: 최고관리자 뷰티샵 심사, 인라인 통제, 미디어 싱크 및 CRUD
  * `App\Http\Controllers\Admin\ProgramController`: 시술 프로그램 및 패키지 번들 구성, 뷰티샵 배포 관리
  * `App\Http\Controllers\Admin\NotificationTemplateController`: 알림 템플릿 CRUD, 원클릭 발송 ON/OFF 토글, 휴지통 복구/영구삭제
  * `App\Http\Controllers\ReservationController`: 사용자 티켓 기반 샵 예약 신청 및 조회
  * `App\Http\Controllers\ShopAdmin\DashboardController`: 뷰티샵 관리자 메인 대시보드
  * `App\Http\Controllers\ShopAdmin\ShopController`: 뷰티샵 모달 등록, 재심사 요청, 샵 정보 수정
  * `App\Http\Controllers\ShopAdmin\ReservationController`: 원장 예약 승인/확정 및 현장 모바일 QR 스캔 차감
  * `App\Http\Middleware\EnsureShopActive`: 1인 다매장 세션(`active_shop_id`) 자동 세팅 및 무매장 신규 점주 온보딩 통과 가드
  * `App\Http\Middleware\EnsureShopOnboarding`: 뷰티샵 관리자 패널 진입 시 4단계(폰인증 ──▶ 샵등록 ──▶ 대기 ──▶ 반려) 뷰 공유 모달 가드

---

## 15. 관리자 및 사용자 레이아웃/기술 자산 명세 (Tech Stack & View Manifest)

### (1) 관리자 통합 레이아웃 구조 (`resources/views/layouts/admin.blade.php`)
최고관리자와 매장관리자는 단일 메인 래퍼 레이아웃을 공유하며, 진입 URL 접두어에 따라 사이드바 컴포넌트를 동적으로 분기합니다.

* **FOUC(깜빡임) 방지 즉시 실행 스크립트**:
  * 데스크톱 브라우저에서 `localStorage.getItem('sidebarCollapsed')` 또는 화면 폭(1200px 이하)을 HTML 파싱 즉시 평가하여 `document.documentElement`에 `preload-collapsed` 클래스를 선적용함으로써 렌더링 시 깜빡임 차단
* **사이드바 분기 로직**:
  * `request()->is('admin*')` ──▶ `@include('admin.partials.sidebar')` (본사 최고관리자 메뉴: 상점관리, 뷰티샵관리, 프로그램관리, 주문/티켓관리, 알림 템플릿 관리 등)
  * `request()->is('shop-admin*')` ──▶ `@include('admin.partials.shop_sidebar')` (뷰티샵 원장 전용 메뉴: 대시보드, 매장정보, 예약접수, 현장 QR인증 등)
* **공통 프레임워크 컴포넌트**: TNB 상단 내비게이션 바(`admin.partials.topbar`), 메인 본문 컨테이너(`content-body`), 푸터(`admin.partials.footer`), 상단 이동 버튼 (`#scrollTopBtn`)
* **글로벌 UI/UX 라이브러리**: Bootstrap 5, FontAwesome 6.4.0 Icons, SweetAlert2 v11 CDN

### (2) 프론트엔드 스크립트 및 스타일 자산 맵 (Frontend Asset Map)

| 분류 | 파일 경로 | 설명 및 주요 역할 |
| :--- | :--- | :--- |
| **공통 인증 모듈 JS** | `public/js/sms_auth.js` | **범용 SMS 본인인증 통합 모듈**: 010/02 자동 하이픈 포맷팅, 카운트다운 타이머, **인증완료 후 번호 실시간 수정 감지 및 `verified-flag` 자동 초기화/복원** |
| | `public/js/biz_auth.js` | 국세청 사업자등록번호 10자리 진위확인 API 비동기 연동 |
| **공통 관리자 JS** | `public/js/admin_custom.js` | 사이드바 토글, 스크롤탑 제어, SweetAlert2 전역 래퍼, 공통 비동기 셋업 |
| **관리자 모듈 JS** | `public/js/admin/admin_shop.js` | 뷰티샵 CRUD 전용 UI, 상태 인라인 변경 AJAX, 다음 주소 연동, 썸네일 제어 |
| | `public/js/admin/admin_program.js` | 프로그램 등록/수정, 3대 실행방식 선택 시 번들 추가 슬롯 렌더링, 샵 배정 피커 UI |
| | `public/js/admin/category_picker.js` | 뷰티샵/프로그램 카테고리 다대다 선택 및 트리 피커 제어 |
| | `public/js/admin/admin_notification.js` | 알림 템플릿 발송 ON/OFF 즉시 토글, 삭제 컨펌, 실시간 모바일 목업 프리뷰 동기화, 대문자 변환 |
| **사용자 모듈 JS** | `public/js/user_custom.js` | 사용자단 SweetAlert2 래퍼(`showSwalAlert`, `showSwalConfirm`, `showToast`), 클립보드 복사, 폰트 크기 조절 |
| | `public/js/address_search.js` | Daum 우편번호 레이어 검색 및 카카오 지도 Geocoder 좌표 자동 추출 |
| | `public/js/reservation_picker.js` | 예약 화면 일자 선택, LBS 지도 카카오 맵 렌더링, 시간 슬롯 동적 계산 |
| **외부 라이브러리 CDN**| `flatpickr.min.js`, `l10n/ko.js` | 마이페이지 생년월일 전용 모바일 최적화 한국어 달력 피커 |
| | `postcode.v2.js` | 카카오 Daum 우편번호 검색 서비스 CDN |
| **공통 관리자 CSS**| `public/css/admin_custom.css` | 관리자 전체 베이스 스타일 및 레이아웃 커스텀 변수 |
| **관리자 모듈 CSS**| `public/css/admin/sidebar.css` | 슬라이드 사이드바 애니메이션 및 축소(Collapsed) 모드 스타일링 |
| | `public/css/admin/admin_notification.css` | 스마트폰 디바이스 목업 프레임, 노치/상태바, 채널별(알림톡/SMS/이메일) 액정 테마 및 말풍선 그리드 스타일 |
| **공통 사용자 CSS** | `public/css/user_custom.css` | 글로뷰 전역 디자인 변수(`--primary-color: #b18977;`, `--radius-fixed: 6px;`), 폰트 유틸(`fs-xs`~`fs-md`), 버튼 시스템 |
| **마이페이지 CSS** | `public/css/mypage/user_mypage.css` | 마이페이지 대시보드 카드, 티켓 패스, 캘린더 큐브, Flatpickr 글로뷰 브론즈 테마 오버라이드 |

### (3) 주요 Blade 뷰 라우팅 및 템플릿 구조

* **인증 뷰 (`resources/views/auth/`)**:
  * `login.blade.php`: 글로뷰 웜 브론즈 프리미엄 테마 적용, 카카오(노랑)/네이버(초록)/구글(화이트&4색G) 3대 간편 로그인 버튼 완비
* **최고관리자 뷰 (`resources/views/admin/`)**:
  * `shops/`: 승인 매장 목록(`index`), 심사 대기(`pending`, `pending_show`), 등록/수정(`create`, `edit`)
  * `programs/`: 시술 상품 목록(`index`), 3대 실행방식 등록/수정(`create`, `edit`)
  * `notifications/`: 알림 템플릿 모바일 목업 카드 그리드 목록(`index`), 실시간 스마트폰 프리뷰 등록(`create`), 수정(`edit`), 단건 상세(`show`)
* **뷰티샵 관리자 뷰 (`resources/views/shop_admin/`)**:
  * `index.blade.php`: 원장 메인 대시보드 (온보딩 모달 4종 렌더링 베이스 화면)
  * `shop/edit.blade.php`: 소유 매장 상세 정보 수정 화면
* **사용자 쇼핑 및 주문 퍼널 (`resources/views/orders/`)**:
  * `checkout.blade.php`: 결제서 작성 (소셜 가입자 폰인증, 무통장 입금자명 입력, 필수 이용약관/환불규정 열람 체크)
  * `complete.blade.php`: 결제 완료 및 무통장 입금 계좌/기한 안내 화면
* **사용자 마이페이지 (`resources/views/mypage/`)**:
  * **`profile/edit.blade.php`**: **회원정보 수정 메인 뷰 (기본 정보 + SMS 인증 + Flatpickr 생년월일 + Daum 우편번호 인라인 검색 + 마케팅 수신동의 + 소셜 분기 비밀번호 등록/변경 카드)**
  * `orders/index.blade.php`: 주문 및 결제 내역 리스트 (입금 대기 중 뱃지 표시)
  * `orders/show.blade.php`: 주문 상세 명세서 및 입금 계좌 안내
  * `tickets/list.blade.php`: 디지털 티켓 보관함 (세로형 티켓 카드, 잔여 회차, [예약하기], [QR보기] 버튼)
  * `tickets/show.blade.php`: 티켓 상세 정보 및 16자리 바코드/QR 코드 뷰어
  * `reservations/create.blade.php`: 방문 일자 선택, LBS 카카오 지도, 방문 슬롯 선택 및 예약 신청 모달
  * `reservations/show.blade.php`: 예약 상세 명세 및 현장 확인용 QR 코드
* **뷰티샵 입점 및 관리 뷰**:
  * `shops/create.blade.php`: 사용자 입점 신청 폼