# 글로뷰 시스템 아키텍처 및 생애주기 가이드 (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회차 또는 복합 프로그램을 결합하여 여러 번에 나누어 개별 예약·사용하는 다회권. 회차는 번들 구성품 수량의 총합으로 자동 계산.

### (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. 티켓 회차 소진 완료: 시술 완료 시 [사용 확인] 클릭 ──▶ 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):
   * 이름 & 성별: col-12 col-md-6
   * 휴대전화번호 & 생년월일: col-12 col-md-6 (동일한 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) 뷰티샵 입점 신청 및 등록 3대 경로
* 경로 A (사용자 직접 입점 신청): /shops/create에서 휴대폰 인증(sms_auth.js) + 사업자 진위확인(biz_auth.js) + LBS 주소(address_search.js) 완료 후 제출 ──▶ status = 'pending', operating_status = 'suspended' 생성 및 세션 바인딩.
* 경로 B (최고관리자 직권 등록 및 사용자 매핑): /admin/shops에서 관리자가 직접 샵 생성 및 원장 계정(user_id) 바인딩 ──▶ status = 'approved', operating_status = 'suspended'(일시중지), 원장 역할(shop) 즉시 동기화.
* 경로 C (권한 선부여 후 점진적 온보딩): 본사가 회원의 역할을 shop으로 선변경 ──▶ 원장이 /shop-admin에 최초 진입 시 EnsureShopActive 및 EnsureShopOnboarding 4단계 모달 가드(폰인증 ──▶ 샵등록 ──▶ 대기 ──▶ 반려) 순차 통과.

### (2) 뷰티샵 심사 및 영업 상태 전이 흐름
* 단계 1 (최고관리자 심사):
  * admin/shops에서 pending 샵 확인 후 [승인] 또는 [반려] 처리.
  * [반려 시]: status = 'rejected' 및 rejection_reason 기록 (원장 온보딩 모달에 반려 사유 노출).
  * [승인 시]: status = 'approved', operating_status = 'suspended'(일시중지 상태로 뷰티샵 관리자에게 권한 이양) 처리 및 원장 계정(users)에 Spatie shop 역할 동기화.
* 단계 2 (뷰티샵 관리자 온보딩 가드 - EnsureShopOnboarding):
  * 승인된 원장이 /shop-admin 패널에 최초 진입할 때, 4단계 온보딩 모달 가드가 작동하여 휴대전화 인증 ──▶ 샵 등록 ──▶ 심사 대기 ──▶ 심사 반려 단계를 순차 제어.
  * 모든 온보딩 관문을 통과하고 활성 매장 세션(active_shop_id)이 바인딩(EnsureShopActive)되면 뷰티샵 관리자 대시보드 및 실무 기능에 정상 접속 가능.

---

## 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\Common\VerifyBusinessController: 국세청 사업자등록번호 10자리 진위 확인 공용 API 컨트롤러
  * App\Http\Controllers\ShopController: 사용자 입점 신청 및 프론트엔드 매장 생성 파이프라인
  * App\Http\Controllers\Admin\ShopController: 최고관리자 뷰티샵 직권 등록(경로 B), 심사, 인라인 통제, 미디어 싱크 및 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 비동기 연동 모듈**: 실시간 10자리 포맷팅 및 통신 성공 시 초록색 (#3b7a57) / 실패 시 붉은색 (#e11d48) 에러 텍스트 강제 오버라이드 |
| **공통 관리자 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 좌표 자동 생성 스크립트**: 주소 선택 시 위도(latitude), 경도(longitude), 권역 카테고리(region_*)를 히든 필드에 자동 주입 |
| | 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: 사용자 입점 신청 폼 (sms_auth.js 휴대폰 인증, biz_auth.js 국세청 사업자 진위확인, address_search.js Daum 주소 및 LBS 카카오 Geocoder 좌표 연동 완료)