diff --git a/guides/2025-12-02_file-attachment-feature.md b/guides/2025-12-02_file-attachment-feature.md
new file mode 100644
index 0000000..6aa4451
--- /dev/null
+++ b/guides/2025-12-02_file-attachment-feature.md
@@ -0,0 +1,163 @@
+# 게시글 파일 첨부 기능 구현 + 공유 스토리지 설정
+
+**작업일**: 2025-12-02
+**저장소**: MNG, API, Docker
+**워크플로우**: code-workflow (분석→수정→검증→정리→커밋)
+
+---
+
+## 개요
+
+게시판 시스템에 파일 첨부 기능을 추가했습니다. 기존의 `board_files` 테이블 대신 범용 `files` 테이블의 polymorphic 관계를 활용합니다.
+
+**추가 작업**: API와 MNG 간 파일 공유를 위한 Docker 공유 볼륨 설정 및 S3 마이그레이션 용이한 구조로 변경
+
+## 변경 파일
+
+### Docker 설정
+| 파일 | 작업 | 설명 |
+|------|------|------|
+| `docker/docker-compose.yml` | 수정 | sam_storage 공유 볼륨 추가 (api, admin, mng) |
+
+### API 저장소
+| 파일 | 작업 | 설명 |
+|------|------|------|
+| `config/filesystems.php` | 수정 | tenant 디스크 공유 경로 + S3 설정 |
+| `database/migrations/2025_12_02_000238_drop_board_files_table.php` | 생성 | board_files 테이블 삭제 |
+
+### MNG 저장소
+| 파일 | 작업 | 설명 |
+|------|------|------|
+| `app/Models/Boards/File.php` | 생성 | Polymorphic 파일 모델 |
+| `app/Models/Boards/Post.php` | 수정 | files() MorphMany 관계 추가 |
+| `app/Services/PostService.php` | 수정 | 파일 업로드/삭제/다운로드 + 경로 패턴 수정 |
+| `app/Http/Controllers/PostController.php` | 수정 | 파일 관련 액션 추가 |
+| `resources/views/posts/create.blade.php` | 수정 | 파일 업로드 UI |
+| `resources/views/posts/show.blade.php` | 수정 | 첨부파일 목록 표시 |
+| `resources/views/posts/edit.blade.php` | 수정 | 기존 파일 관리 + 새 파일 업로드 |
+| `routes/web.php` | 수정 | 파일 라우트 추가 |
+| `config/filesystems.php` | 수정 | tenant 디스크 공유 경로 + S3 설정 |
+| `storage/app/tenants/.gitignore` | 생성 | 업로드 파일 Git 제외 |
+
+## 기술 상세
+
+### Polymorphic 관계
+```php
+// Post -> files() MorphMany
+$post->files; // Collection of File models
+
+// File -> fileable() MorphTo
+$file->fileable; // Returns Post model
+```
+
+### 파일 저장 경로 (공유 스토리지)
+```
+Docker 볼륨: sam_storage → /var/www/shared-storage
+실제 경로: /var/www/shared-storage/tenants/{tenant_id}/posts/{year}/{month}/{stored_name}
+DB 저장: {tenant_id}/posts/{year}/{month}/{stored_name}
+```
+
+### 공유 스토리지 아키텍처
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Docker Volume: sam_storage │
+│ /var/www/shared-storage/tenants │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
+│ │ API │ │ MNG │ │ Admin │ │
+│ │Container│ │Container│ │Container│ │
+│ └────┬────┘ └────┬────┘ └────┬────┘ │
+│ │ │ │ │
+│ └────────────────┴────────────────┘ │
+│ │ │
+│ Storage::disk('tenant') │
+│ │ │
+│ ┌─────────────────────┴─────────────────────┐ │
+│ │ /var/www/shared-storage/tenants │ │
+│ │ ├── {tenant_id}/ │ │
+│ │ │ ├── posts/2025/12/xxx.pdf │ │
+│ │ │ ├── products/2025/12/yyy.jpg │ │
+│ │ │ └── documents/2025/12/zzz.docx │ │
+│ └────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### S3 마이그레이션 방법
+```bash
+# .env 설정 변경만으로 S3 전환 가능
+TENANT_STORAGE_DRIVER=s3
+AWS_ACCESS_KEY_ID=your_key
+AWS_SECRET_ACCESS_KEY=your_secret
+AWS_DEFAULT_REGION=ap-northeast-2
+AWS_BUCKET=sam-storage
+```
+
+### 게시판 설정
+- `allow_files`: 파일 첨부 허용 여부
+- `max_file_count`: 최대 파일 개수
+- `max_file_size`: 최대 파일 크기 (KB)
+
+### 새 라우트
+```
+GET boards/{board}/posts/{post}/files/{fileId}/download # 다운로드
+POST boards/{board}/posts/{post}/files # 업로드 (AJAX)
+DELETE boards/{board}/posts/{post}/files/{fileId} # 삭제 (AJAX)
+```
+
+### File 모델 주요 메서드
+- `fileable()`: Polymorphic 관계
+- `download()`: StreamedResponse 반환
+- `getFormattedSize()`: 사람이 읽기 쉬운 파일 크기
+- `isImage()`: 이미지 파일 여부
+- `permanentDelete()`: 실제 파일 + DB 레코드 삭제
+
+### PostService 주요 메서드
+- `uploadFiles(Post, array)`: 파일 업로드 및 저장
+- `deleteFile(Post, fileId)`: 파일 소프트 삭제
+- `downloadFile(Post, fileId)`: 파일 다운로드 응답
+
+## UI 기능
+
+### 글쓰기 (create.blade.php)
+- 드래그앤드롭 파일 업로드 영역 ✅
+- 파일 선택 시 미리보기 목록
+- 파일 개수/크기 제한 클라이언트 검증
+- **드래그앤드롭 JS 이벤트** (dragenter, dragover, dragleave, drop)
+- **시각적 피드백** (드래그 시 테두리 파란색 변경)
+
+### 글보기 (show.blade.php)
+- 첨부파일 섹션 (파일 있을 때만 표시)
+- 이미지/문서 아이콘 구분
+- 다운로드 버튼
+
+### 글수정 (edit.blade.php)
+- 기존 첨부파일 목록 (삭제 버튼 포함)
+- AJAX 파일 삭제 (확인 후 즉시 반영)
+- 새 파일 추가 영역
+- **드래그앤드롭 JS 이벤트** (dragenter, dragover, dragleave, drop)
+- **시각적 피드백** (드래그 시 테두리 파란색 변경)
+- **기존 파일 개수 고려** (최대 파일 수 체크 시 기존 파일 포함)
+
+## 검증 결과
+
+- [x] PHP 문법 검증 통과
+- [x] 라우트 등록 확인
+- [x] tenant 디스크 설정 확인
+- [x] Pint 코드 포맷팅 완료
+
+## 다음 단계 (커밋)
+
+### API 저장소
+```bash
+cd /Users/kent/Works/@KD_SAM/SAM/api
+git add .
+git commit -m "feat(SAM-API): board_files 테이블 삭제 마이그레이션"
+```
+
+### MNG 저장소
+```bash
+cd /Users/kent/Works/@KD_SAM/SAM/mng
+git add .
+git commit -m "feat(SAM-MNG): 게시글 파일 첨부 기능 구현"
+```
\ No newline at end of file
diff --git a/guides/ai-config-설정.md b/guides/ai-config-설정.md
new file mode 100644
index 0000000..de03371
--- /dev/null
+++ b/guides/ai-config-설정.md
@@ -0,0 +1,325 @@
+# AI 및 스토리지 설정 기술문서
+
+> 최종 업데이트: 2026-01-29
+
+## 개요
+
+SAM MNG 시스템의 AI API 및 클라우드 스토리지(GCS) 설정을 관리하는 기능입니다.
+관리자 UI에서 설정하거나, `.env` 환경변수로 설정할 수 있습니다.
+
+**접근 경로**: 시스템 관리 > AI 설정 (`/system/ai-config`)
+
+---
+
+## 지원 Provider
+
+### AI Provider
+| Provider | 용도 | 기본 모델 |
+|----------|------|----------|
+| `gemini` | Google Gemini (명함 OCR, AI 어시스턴트) | gemini-2.0-flash |
+| `claude` | Anthropic Claude | claude-sonnet-4-20250514 |
+| `openai` | OpenAI GPT | gpt-4o |
+
+### Storage Provider
+| Provider | 용도 |
+|----------|------|
+| `gcs` | Google Cloud Storage (음성 녹음 백업) |
+
+---
+
+## 데이터베이스 구조
+
+### 테이블: `ai_configs`
+
+```sql
+CREATE TABLE ai_configs (
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL, -- 설정 이름 (예: "Production Gemini")
+ provider VARCHAR(20) NOT NULL, -- gemini, claude, openai, gcs
+ api_key VARCHAR(255) NOT NULL, -- API 키 (GCS는 'gcs_service_account')
+ model VARCHAR(100) NOT NULL, -- 모델명 (GCS는 '-')
+ base_url VARCHAR(255) NULL, -- 커스텀 Base URL
+ description TEXT NULL, -- 설명
+ is_active BOOLEAN DEFAULT FALSE, -- 활성화 여부 (provider당 1개만)
+ options JSON NULL, -- 추가 옵션 (아래 참조)
+ created_at TIMESTAMP,
+ updated_at TIMESTAMP,
+ deleted_at TIMESTAMP NULL -- Soft Delete
+);
+```
+
+### options JSON 구조
+
+**AI Provider (Gemini Vertex AI)**:
+```json
+{
+ "auth_type": "vertex_ai",
+ "project_id": "my-gcp-project",
+ "region": "us-central1",
+ "service_account_path": "/var/www/sales/apikey/google_service_account.json"
+}
+```
+
+**AI Provider (API Key)**:
+```json
+{
+ "auth_type": "api_key"
+}
+```
+
+**GCS Provider**:
+```json
+{
+ "bucket_name": "my-bucket-name",
+ "service_account_path": "/var/www/sales/apikey/google_service_account.json",
+ "service_account_json": { ... } // 또는 JSON 직접 입력
+}
+```
+
+---
+
+## 설정 우선순위
+
+### GCS 설정 우선순위
+
+```
+1. DB 설정 (ai_configs 테이블의 활성화된 gcs provider)
+ ↓ 없으면
+2. 환경변수 (.env의 GCS_BUCKET_NAME, GCS_SERVICE_ACCOUNT_PATH)
+ ↓ 없으면
+3. 레거시 파일 (/sales/apikey/gcs_config.txt, google_service_account.json)
+```
+
+### AI 설정 우선순위
+
+```
+1. DB 설정 (ai_configs 테이블의 활성화된 provider)
+ ↓ 없으면
+2. 환경변수 (.env의 GEMINI_API_KEY 등)
+ ↓ 없으면
+3. 레거시 파일
+```
+
+---
+
+## 환경변수 설정 (.env)
+
+### GCS 설정
+```env
+# Google Cloud Storage (음성 녹음 백업)
+GCS_BUCKET_NAME=your-bucket-name
+GCS_SERVICE_ACCOUNT_PATH=/var/www/sales/apikey/google_service_account.json
+GCS_USE_DB_CONFIG=true # false면 DB 설정 무시, .env만 사용
+```
+
+### AI 설정 (참고)
+```env
+# Google Gemini API
+GEMINI_API_KEY=your-api-key
+GEMINI_PROJECT_ID=your-project-id
+```
+
+---
+
+## 관련 파일 목록
+
+### 모델
+| 파일 | 설명 |
+|------|------|
+| `app/Models/System/AiConfig.php` | AI 설정 Eloquent 모델 |
+
+### 컨트롤러
+| 파일 | 설명 |
+|------|------|
+| `app/Http/Controllers/System/AiConfigController.php` | CRUD + 연결 테스트 |
+
+### 서비스
+| 파일 | 설명 |
+|------|------|
+| `app/Services/GoogleCloudStorageService.php` | GCS 업로드/다운로드/삭제 |
+| `app/Services/GeminiService.php` | Gemini API 호출 (명함 OCR 등) |
+
+### 설정
+| 파일 | 설명 |
+|------|------|
+| `config/gcs.php` | GCS 환경변수 설정 |
+
+### 뷰
+| 파일 | 설명 |
+|------|------|
+| `resources/views/system/ai-config/index.blade.php` | AI 설정 관리 페이지 |
+
+### 라우트
+```php
+// routes/web.php
+Route::prefix('system')->name('system.')->group(function () {
+ Route::resource('ai-config', AiConfigController::class)->except(['show', 'create', 'edit']);
+ Route::post('ai-config/{id}/toggle', [AiConfigController::class, 'toggle'])->name('ai-config.toggle');
+ Route::post('ai-config/test', [AiConfigController::class, 'test'])->name('ai-config.test');
+ Route::post('ai-config/test-gcs', [AiConfigController::class, 'testGcs'])->name('ai-config.test-gcs');
+});
+```
+
+---
+
+## 주요 메서드
+
+### AiConfig 모델
+
+```php
+// Provider별 활성 설정 조회
+AiConfig::getActiveGemini(); // ?AiConfig
+AiConfig::getActiveClaude(); // ?AiConfig
+AiConfig::getActiveGcs(); // ?AiConfig
+AiConfig::getActive('openai'); // ?AiConfig
+
+// GCS 전용 메서드
+$config->getBucketName(); // ?string
+$config->getServiceAccountJson(); // ?array
+$config->getServiceAccountPath(); // ?string
+$config->isGcs(); // bool
+
+// Vertex AI 전용 메서드
+$config->isVertexAi(); // bool
+$config->getProjectId(); // ?string
+$config->getRegion(); // string (기본: us-central1)
+```
+
+### GoogleCloudStorageService
+
+```php
+$gcs = new GoogleCloudStorageService();
+
+// 사용 가능 여부
+$gcs->isAvailable(); // bool
+
+// 설정 소스 확인
+$gcs->getConfigSource(); // 'db' | 'env' | 'legacy' | 'none'
+
+// 파일 업로드
+$gcsUri = $gcs->upload($localPath, $objectName); // 'gs://bucket/object' | null
+
+// 서명된 다운로드 URL (60분 유효)
+$url = $gcs->getSignedUrl($objectName, 60); // string | null
+
+// 파일 삭제
+$gcs->delete($objectName); // bool
+```
+
+---
+
+## UI 구조
+
+### 탭 구성
+- **AI 설정 탭**: Gemini, Claude, OpenAI 설정 관리
+- **스토리지 설정 탭**: GCS 설정 관리
+
+### 기능
+- 설정 추가/수정/삭제
+- 활성화/비활성화 토글 (provider당 1개만 활성화)
+- 연결 테스트
+
+---
+
+## 사용 예시
+
+### GCS 업로드 (ConsultationController)
+
+```php
+use App\Services\GoogleCloudStorageService;
+
+public function uploadAudio(Request $request)
+{
+ // 파일 저장
+ $path = $file->store("tenant/consultations/{$tenantId}");
+ $fullPath = storage_path('app/' . $path);
+
+ // 10MB 이상이면 GCS에도 업로드
+ if ($file->getSize() > 10 * 1024 * 1024) {
+ $gcs = new GoogleCloudStorageService();
+ if ($gcs->isAvailable()) {
+ $gcsUri = $gcs->upload($fullPath, "consultations/{$tenantId}/" . basename($path));
+ }
+ }
+}
+```
+
+### 명함 OCR (GeminiService)
+
+```php
+use App\Services\GeminiService;
+
+$gemini = new GeminiService();
+$result = $gemini->extractBusinessCard($imagePath);
+```
+
+---
+
+## 배포 가이드
+
+### 서버 최초 설정
+
+1. `.env` 파일에 GCS 설정 추가:
+ ```env
+ GCS_BUCKET_NAME=production-bucket
+ GCS_SERVICE_ACCOUNT_PATH=/var/www/sales/apikey/google_service_account.json
+ ```
+
+2. 서비스 계정 JSON 파일 배치:
+ ```
+ /var/www/sales/apikey/google_service_account.json
+ ```
+
+3. 설정 캐시 갱신:
+ ```bash
+ docker exec sam-mng-1 php artisan config:cache
+ ```
+
+### 이후 배포
+- 코드 push만으로 동작 (설정 변경 불필요)
+- UI에서 오버라이드하고 싶을 때만 DB 설정 사용
+
+---
+
+## 트러블슈팅
+
+### GCS 업로드 실패
+
+1. **설정 확인**:
+ ```php
+ $gcs = new GoogleCloudStorageService();
+ dd($gcs->isAvailable(), $gcs->getConfigSource(), $gcs->getBucketName());
+ ```
+
+2. **로그 확인**:
+ ```bash
+ docker exec sam-mng-1 tail -f storage/logs/laravel.log | grep GCS
+ ```
+
+3. **일반적인 원인**:
+ - 서비스 계정 파일 경로 오류
+ - 서비스 계정에 Storage 권한 없음
+ - 버킷 이름 오타
+
+### AI API 연결 실패
+
+1. **API 키 확인**: UI에서 "테스트" 버튼 클릭
+2. **모델명 확인**: provider별 지원 모델 확인
+3. **할당량 확인**: Google Cloud Console에서 API 할당량 확인
+
+---
+
+## 레거시 파일 위치 (참고)
+
+Docker 컨테이너 내부 경로:
+```
+/var/www/sales/apikey/
+├── gcs_config.txt # bucket_name=xxx
+├── google_service_account.json # GCP 서비스 계정 키
+└── gemini_api_key.txt # Gemini API 키 (레거시)
+```
+
+호스트 경로 (mng 기준):
+```
+../sales/apikey/
+```
diff --git a/guides/archive-restore-feature-analysis.md b/guides/archive-restore-feature-analysis.md
new file mode 100644
index 0000000..6d311b6
--- /dev/null
+++ b/guides/archive-restore-feature-analysis.md
@@ -0,0 +1,262 @@
+# Archive & Restore Feature Analysis
+
+**날짜:** 2025-11-30
+**작업자:** Claude Code
+**요청:** 영구 삭제 데이터 복원 기능 구현
+
+---
+
+## 1. 요청 내용
+
+- `https://mng.sam.kr/archived-records` 에서 영구 삭제된 데이터를 복원할 수 있는 기능
+- 삭제/복구 프로세스 정립:
+ - 일반 관리자/테넌트: Soft Delete
+ - 슈퍼관리자: 영구 삭제 가능 → archived_records에 저장
+ - 영구 삭제 데이터: 복원 가능해야 함
+- UI 개선: 작업 설명 컬럼 개선
+
+---
+
+## 2. 현재 상태 분석
+
+### 2.1 forceDelete 사용 서비스 (8개)
+
+모든 서비스가 **아카이브 없이** 바로 영구 삭제:
+
+| 서비스 | 메서드 | 삭제 대상 | 파일 위치 |
+|--------|--------|----------|-----------|
+| `TenantService` | `forceDeleteTenant()` | 테넌트 + 부서/메뉴/역할 | `app/Services/TenantService.php:115` |
+| `UserService` | `forceDeleteUser()` | 사용자 | `app/Services/UserService.php:232` |
+| `DepartmentService` | `forceDeleteDepartment()` | 부서 | `app/Services/DepartmentService.php:171` |
+| `MenuService` | `forceDeleteMenu()` | 메뉴 | `app/Services/MenuService.php:281` |
+| `BoardService` | `forceDeleteBoard()` | 게시판 | `app/Services/BoardService.php:141` |
+| `ProjectService` | `forceDeleteProject()` | 프로젝트 | `app/Services/ProjectManagement/ProjectService.php:134` |
+| `IssueService` | `forceDeleteIssue()` | 이슈 | `app/Services/ProjectManagement/IssueService.php:160` |
+| `TaskService` | `forceDeleteTask()` | 작업 | `app/Services/ProjectManagement/TaskService.php:168` |
+
+### 2.2 현재 DB 스키마
+
+**archived_records 테이블:**
+```
+id bigint PK
+batch_id char(36) -- UUID, 그룹핑용
+batch_description varchar(255) -- 배치 설명
+record_type varchar(50) -- ✅ varchar로 변경됨 (기존 enum)
+original_id bigint -- 원본 레코드 ID
+main_data json -- 원본 데이터 (JSON)
+schema_version varchar(50) -- 스키마 버전
+deleted_by bigint FK -- 삭제자
+deleted_at timestamp -- 삭제 시간
+notes text -- 메모
+created_at, updated_at, created_by, updated_by
+```
+
+**archived_record_relations 테이블:**
+```
+id bigint PK
+archived_record_id bigint FK -- archived_records.id
+table_name varchar(100) -- 관련 테이블명
+data json -- 관련 데이터 (JSON)
+record_count int -- 레코드 수
+created_at, updated_at, created_by, updated_by
+```
+
+### 2.3 문제점
+
+1. **아카이브 생성 코드 없음**: `ArchivedRecord::create()` 호출하는 곳이 없음
+2. ✅ ~~**record_type enum 제한**~~: varchar로 변경 완료
+3. **복원 기능 없음**: RestoreService 미존재
+4. **데이터 유실**: forceDelete 시 데이터가 완전히 삭제됨
+
+---
+
+## 3. 구현 계획
+
+### Phase 1: 인프라 구축 (이번 작업)
+
+#### 3.1 마이그레이션 ✅ 완료
+- `record_type` enum → varchar(50) 변경 완료
+
+#### 3.2 ArchiveService 생성
+```php
+class ArchiveService {
+ // 단일 모델 아카이브
+ public function archiveModel(Model $model, array $relations = [], ?string $batchId = null): ArchivedRecord
+
+ // 배치 아카이브 (여러 모델)
+ public function archiveBatch(Collection $models, string $description, array $relations = []): string
+
+ // 모델별 record_type 매핑
+ private function getRecordType(Model $model): string
+}
+```
+
+#### 3.3 RestoreService 생성
+```php
+class RestoreService {
+ // 단일 레코드 복원
+ public function restoreRecord(ArchivedRecord $record): Model
+
+ // 배치 전체 복원
+ public function restoreBatch(string $batchId): Collection
+
+ // 관계 데이터 복원
+ private function restoreRelations(ArchivedRecord $record): void
+}
+```
+
+#### 3.4 기존 서비스 수정 (TenantService, UserService 먼저)
+
+#### 3.5 UI 개선
+- 복원 버튼 추가
+- 라우트 추가
+- 컨트롤러 메서드 추가
+
+---
+
+## 4. 수정 대상 파일
+
+| # | 파일 | 작업 | 상태 |
+|---|------|------|------|
+| 1 | `database/migrations/2025_11_30_*_modify_archived_records_record_type_to_varchar.php` | 신규 | ✅ 완료 |
+| 2 | `app/Services/ArchiveService.php` | 신규 | 🔄 진행 중 |
+| 3 | `app/Services/RestoreService.php` | 신규 | ⏳ 대기 |
+| 4 | `app/Services/TenantService.php` | 수정 | ⏳ 대기 |
+| 5 | `app/Services/UserService.php` | 수정 | ⏳ 대기 |
+| 6 | `app/Http/Controllers/ArchivedRecordController.php` | 수정 | ⏳ 대기 |
+| 7 | `routes/web.php` | 수정 | ⏳ 대기 |
+| 8 | `resources/views/archived-records/show.blade.php` | 수정 | ⏳ 대기 |
+
+---
+
+## 5. record_type 매핑
+
+```php
+$recordTypeMap = [
+ Tenant::class => 'tenant',
+ User::class => 'user',
+ Department::class => 'department',
+ Menu::class => 'menu',
+ Role::class => 'role',
+ Board::class => 'board',
+ Project::class => 'project',
+ Issue::class => 'issue',
+ Task::class => 'task',
+];
+```
+
+---
+
+## 6. 복원 로직 흐름
+
+```
+1. ArchivedRecord 조회 (batch_id 또는 id)
+2. main_data에서 원본 데이터 추출
+3. 원본 테이블에 INSERT (새 ID 할당)
+4. relations 복원 (ArchivedRecordRelation)
+5. ArchivedRecord 삭제
+6. 트랜잭션 커밋
+```
+
+---
+
+## 7. 주의 사항
+
+- **FK 제약**: 복원 시 관계 테이블 순서 중요 (부모 먼저)
+- **ID 할당**: 복원 시 새 ID 할당 (original_id는 참조용)
+- **tenant_id 무결성**: Multi-tenant 데이터 복원 시 tenant_id 검증
+- **트랜잭션**: 복원 실패 시 롤백 필수
+
+---
+
+## 8. Phase 2: 테넌트 필터링 기능 추가 (신규 요청)
+
+### 8.1 요청 내용
+
+1. **대상 테넌트 필드 추가**:
+ - 테넌트 삭제 시: 어떤 테넌트인지 표시
+ - 사용자 삭제 시: 어떤 테넌트 소속인지 표시
+2. **상단 테넌트 선택 필터링**: 현재 선택된 테넌트의 아카이브만 표시
+
+### 8.2 현재 문제점
+
+- `archived_records` 테이블에 `tenant_id` 컬럼 없음
+- 사용자 삭제 시 소속 테넌트 정보 저장 안 됨
+- 테넌트 선택 필터링 불가
+
+### 8.3 해결 방안
+
+#### 방안 A: tenant_id 컬럼 추가 (권장)
+```
+장점:
+- 직접 필터링 가능 (성능 좋음)
+- 명확한 테넌트 소속 관계
+- 인덱스 활용 가능
+
+단점:
+- 마이그레이션 필요
+- 기존 데이터 처리 필요 (main_data에서 추출)
+```
+
+#### 방안 B: main_data에서 JSON 추출 (현재 방식)
+```
+장점:
+- DB 스키마 변경 없음
+
+단점:
+- JSON 추출 쿼리 복잡
+- 성능 저하 (인덱스 불가)
+- 사용자의 경우 tenant_id가 main_data에 없을 수 있음
+```
+
+### 8.4 권장 방안: A (tenant_id 컬럼 추가)
+
+#### 수정 대상 파일
+
+| # | 저장소 | 파일 | 작업 |
+|---|--------|------|------|
+| 1 | **api/** | `database/migrations/2025_12_01_*_add_tenant_id_to_archived_records.php` | 신규 - DB 마이그레이션 |
+| 2 | mng/ | `app/Services/ArchiveService.php` | 수정 - tenant_id 저장 로직 |
+| 3 | mng/ | `app/Services/ArchivedRecordService.php` | 수정 - 테넌트 필터링 |
+| 4 | mng/ | `app/Models/Archives/ArchivedRecord.php` | 수정 - fillable, 관계 추가 |
+| 5 | mng/ | `resources/views/archived-records/partials/table.blade.php` | 수정 - 대상 테넌트 표시 |
+
+> **NOTE**: DB 마이그레이션은 `api/` 저장소에서 관리됨. mng/에서는 모델과 서비스만 수정.
+
+#### 마이그레이션 내용 (api/)
+```php
+// api/database/migrations/2025_12_01_*_add_tenant_id_to_archived_records.php
+Schema::table('archived_records', function (Blueprint $table) {
+ $table->unsignedBigInteger('tenant_id')->nullable()->after('record_type');
+ $table->foreign('tenant_id')->references('id')->on('tenants')->nullOnDelete();
+ $table->index('tenant_id');
+});
+```
+
+#### tenant_id 결정 로직
+```
+- 테넌트 삭제: tenant_id = 삭제되는 테넌트의 ID (자기 자신)
+- 사용자 삭제: tenant_id = session('selected_tenant_id') (현재 선택된 테넌트)
+- 부서/메뉴/역할 삭제: tenant_id = 해당 레코드의 tenant_id
+```
+
+#### 기존 데이터 처리
+```sql
+-- 테넌트 타입: main_data에서 id 추출
+UPDATE archived_records
+SET tenant_id = JSON_UNQUOTE(JSON_EXTRACT(main_data, '$.id'))
+WHERE record_type = 'tenant' AND tenant_id IS NULL;
+
+-- 사용자 타입: main_data에 tenant_id가 없으므로 NULL 유지
+-- (또는 user_tenants 관계에서 추출 - 복잡)
+```
+
+### 8.5 UI 변경
+
+#### 목록 테이블 컬럼
+| 작업 설명 | 대상 테넌트 | 대상 정보 | 레코드 타입 | ... |
+
+#### 필터링
+- 상단 테넌트 선택 시 `session('selected_tenant_id')` 기준 필터링
+- 슈퍼관리자: 전체 보기 가능
+- 일반 관리자: 소속 테넌트만 보기
diff --git a/guides/barobill-members-migration.md b/guides/barobill-members-migration.md
new file mode 100644
index 0000000..7b6ff96
--- /dev/null
+++ b/guides/barobill-members-migration.md
@@ -0,0 +1,144 @@
+# 바로빌 회원사관리 - 레거시 마이그레이션 계획
+
+> 레거시 소스: `sam/sales/barobill/registration/index.php`
+
+## 1. 레거시 분석
+
+### 기술 스택
+- Frontend: React 18 + Babel (브라우저 트랜스파일링)
+- Backend: PHP + PDO (api.php)
+- UI: Tailwind CSS + Lucide Icons
+
+### 데이터베이스 구조 (`barobill_members` 테이블)
+
+| 필드명 | 타입 | 설명 |
+|--------|------|------|
+| `id` | INT | PK, Auto Increment |
+| `biz_no` | VARCHAR | 사업자번호 (Unique) |
+| `corp_name` | VARCHAR | 상호명 |
+| `ceo_name` | VARCHAR | 대표자명 |
+| `addr` | VARCHAR | 주소 |
+| `biz_type` | VARCHAR | 업태 |
+| `biz_class` | VARCHAR | 종목 |
+| `barobill_id` | VARCHAR | 바로빌 아이디 |
+| `barobill_pwd` | VARCHAR | 바로빌 비밀번호 (해시) |
+| `manager_name` | VARCHAR | 담당자명 |
+| `manager_email` | VARCHAR | 담당자 이메일 |
+| `manager_hp` | VARCHAR | 담당자 전화번호 |
+| `created_at` | TIMESTAMP | 생성일시 |
+
+### API 엔드포인트 (레거시)
+
+| Method | Endpoint | 설명 |
+|--------|----------|------|
+| GET | `api.php` | 전체 목록 조회 |
+| GET | `api.php?id={id}` | 단일 조회 |
+| POST | `api.php` | 신규 등록 (사업자번호 중복 체크) |
+| PUT | `api.php` | 정보 수정 |
+| DELETE | `api.php?id={id}` | 삭제 |
+
+### UI 기능
+
+1. **통계 카드 (4개)**
+ - 연동 회원사 수 (DB 실시간)
+ - API 키 상태
+ - 트래픽 상태
+ - 서버 상태
+
+2. **탭 네비게이션**
+ - 목록 조회
+ - 신규 등록
+
+3. **목록 테이블 컬럼**
+ - 사업자번호
+ - 상호 / 대표자
+ - 바로빌 ID
+ - 담당자 정보
+ - 작업 (수정/삭제)
+
+4. **등록 폼 필드**
+ - 사업자번호 (필수)
+ - 상호명 (필수)
+ - 대표자명 (필수)
+ - 업태
+ - 종목
+ - 주소
+ - 바로빌 아이디 (필수, 등록 시만)
+ - 비밀번호 (필수, 등록 시만)
+ - 담당자명
+ - 담당자 HP
+ - 담당자 이메일
+ - **자동완성 버튼** (테스트 데이터 입력)
+
+5. **수정 모달**
+ - 등록 폼과 동일 (아이디/비밀번호 제외)
+
+---
+
+## 2. Laravel 마이그레이션 계획
+
+### 생성할 파일 목록
+
+#### Model & Migration
+```
+app/Models/BarobillMember.php
+database/migrations/xxxx_create_barobill_members_table.php
+```
+
+#### Controller
+```
+app/Http/Controllers/Barobill/BarobillController.php (이미 생성됨)
+app/Http/Controllers/Api/Admin/BarobillController.php (API용)
+```
+
+#### Views
+```
+resources/views/barobill/members/index.blade.php (이미 생성됨 - 업데이트 필요)
+resources/views/barobill/members/partials/table.blade.php
+resources/views/barobill/members/partials/form.blade.php
+resources/views/barobill/members/partials/modal-edit.blade.php
+```
+
+#### Routes
+```php
+// Web Routes (이미 추가됨)
+Route::prefix('barobill')->name('barobill.')->group(function () {
+ Route::get('/members', [BarobillController::class, 'members'])->name('members.index');
+});
+
+// API Routes (추가 필요)
+Route::prefix('barobill')->name('barobill.')->group(function () {
+ Route::get('/members', [BarobillApiController::class, 'index']);
+ Route::get('/members/{id}', [BarobillApiController::class, 'show']);
+ Route::post('/members', [BarobillApiController::class, 'store']);
+ Route::put('/members/{id}', [BarobillApiController::class, 'update']);
+ Route::delete('/members/{id}', [BarobillApiController::class, 'destroy']);
+});
+```
+
+### 구현 순서
+
+1. [ ] Migration 생성 및 실행
+2. [ ] Model 생성 (fillable, casts 설정)
+3. [ ] API Controller 생성 (CRUD)
+4. [ ] API Routes 추가
+5. [ ] View 업데이트 (HTMX + Blade)
+ - 통계 카드
+ - 탭 (목록/등록)
+ - 테이블 (HTMX 로드)
+ - 등록 폼
+ - 수정 모달
+6. [ ] 테스트
+
+---
+
+## 3. 참고 사항
+
+### 레거시 코드 위치
+- Frontend: `sam/sales/barobill/registration/index.php`
+- Backend API: `sam/sales/barobill/registration/api.php`
+
+### 주의 사항
+- 사업자번호 중복 체크 로직 필요
+- 비밀번호는 해시 저장 (password_hash)
+- 바로빌 API 연동은 별도 Service 클래스로 분리 권장
diff --git a/guides/super-admin-protection.md b/guides/super-admin-protection.md
new file mode 100644
index 0000000..5881fba
--- /dev/null
+++ b/guides/super-admin-protection.md
@@ -0,0 +1,174 @@
+# Super Admin Protection Feature
+
+**날짜:** 2025-12-01
+**작업자:** Claude Code
+**요청:** 슈퍼관리자 보호 및 복원/영구삭제 권한 분리
+
+---
+
+## 1. 요구사항
+
+### 1.1 슈퍼관리자 보호
+- 일반관리자는 슈퍼관리자를 **볼 수 없음** (목록에서 숨김)
+- 일반관리자는 슈퍼관리자를 **수정/삭제할 수 없음**
+- 슈퍼관리자만 다른 슈퍼관리자를 관리 가능
+
+### 1.2 복원/영구삭제 권한 분리
+- **복원 (Restore)**: 일반관리자도 가능
+- **영구삭제 (Force Delete)**: 슈퍼관리자 전용
+
+---
+
+## 2. 구현 내용
+
+### 2.1 라우트 수정 (`routes/api.php`)
+
+8개 엔티티의 restore 라우트를 `super.admin` 미들웨어 밖으로 이동:
+
+| 엔티티 | 라인 | 복원 라우트 | 영구삭제 라우트 |
+|--------|------|-------------|-----------------|
+| Tenants | 42-48 | `POST /{id}/restore` | `DELETE /{id}/force` (super.admin) |
+| Departments | 76-82 | `POST /{id}/restore` | `DELETE /{id}/force` (super.admin) |
+| Users | 93-99 | `POST /{id}/restore` | `DELETE /{id}/force` (super.admin) |
+| Menus | 117-123 | `POST /{id}/restore` | `DELETE /{id}/force` (super.admin) |
+| Boards | 151-157 | `POST /{id}/restore` | `DELETE /{id}/force` (super.admin) |
+| PM Projects | 234-240 | `POST /{id}/restore` | `DELETE /{id}/force` (super.admin) |
+| PM Tasks | 260-266 | `POST /{id}/restore` | `DELETE /{id}/force` (super.admin) |
+| PM Issues | 292-298 | `POST /{id}/restore` | `DELETE /{id}/force` (super.admin) |
+
+**패턴:**
+```php
+// 복원 (일반관리자 가능)
+Route::post('/{id}/restore', [Controller::class, 'restore'])->name('restore');
+
+// 슈퍼관리자 전용 액션 (영구삭제)
+Route::middleware('super.admin')->group(function () {
+ Route::delete('/{id}/force', [Controller::class, 'forceDestroy'])->name('forceDestroy');
+});
+```
+
+### 2.2 서비스 레이어 수정
+
+#### `app/Services/UserService.php`
+```php
+public function canAccessUser(int $targetUserId): bool
+{
+ // withTrashed()를 사용하여 soft-deleted 사용자도 확인 (복원 시 필요)
+ $targetUser = User::withTrashed()->find($targetUserId);
+ $currentUser = auth()->user();
+
+ // 대상 사용자가 슈퍼관리자이고 현재 사용자가 슈퍼관리자가 아니면 접근 불가
+ if ($targetUser?->is_super_admin && ! $currentUser?->is_super_admin) {
+ return false;
+ }
+
+ return true;
+}
+```
+
+#### `app/Services/UserPermissionService.php`
+```php
+public function canModifyUser(int $targetUserId): bool
+{
+ // withTrashed()를 사용하여 일관성 유지
+ $targetUser = User::withTrashed()->find($targetUserId);
+ $currentUser = auth()->user();
+
+ if ($targetUser?->is_super_admin && ! $currentUser?->is_super_admin) {
+ return false;
+ }
+
+ return true;
+}
+```
+
+**핵심 수정**: `User::find()` → `User::withTrashed()->find()`
+- Soft-deleted 사용자도 조회 가능하게 변경
+- 복원 작업 시 권한 체크가 정상 작동
+
+### 2.3 뷰 레이어 수정
+
+6개 테이블 뷰에 권한별 버튼 표시 로직 적용:
+
+| 파일 | 복원 버튼 | 영구삭제 버튼 |
+|------|-----------|---------------|
+| `users/partials/table.blade.php` | `$canModify` 체크 | `is_super_admin` 체크 |
+| `users/partials/modal-info.blade.php` | 슈퍼관리자이거나 대상이 일반사용자 | - |
+| `departments/partials/table.blade.php` | 항상 표시 | `is_super_admin` 체크 |
+| `menus/partials/table.blade.php` | 항상 표시 | `is_super_admin` 체크 |
+| `boards/partials/table.blade.php` | 항상 표시 | `is_super_admin` 체크 |
+| `tenants/partials/table.blade.php` | 항상 표시 | `is_super_admin` 체크 |
+| `project-management/projects/partials/table.blade.php` | 항상 표시 | `is_super_admin` 체크 |
+
+**Blade 패턴:**
+```blade
+@if($item->deleted_at)
+
+
+ @if(auth()->user()?->is_super_admin)
+
+ @endif
+@endif
+```
+
+---
+
+## 3. 수정된 파일 목록
+
+### 라우트
+- `routes/api.php` - 8개 엔티티 restore 라우트 분리
+
+### 서비스
+- `app/Services/UserService.php` - `canAccessUser()` withTrashed 적용
+- `app/Services/UserPermissionService.php` - `canModifyUser()` withTrashed 적용
+
+### 뷰 (Blade)
+- `resources/views/users/partials/table.blade.php`
+- `resources/views/users/partials/modal-info.blade.php`
+- `resources/views/departments/partials/table.blade.php`
+- `resources/views/menus/partials/table.blade.php`
+- `resources/views/boards/partials/table.blade.php`
+- `resources/views/tenants/partials/table.blade.php`
+- `resources/views/project-management/projects/partials/table.blade.php`
+
+---
+
+## 4. 테스트 시나리오
+
+### 4.1 일반관리자 테스트
+- [ ] 사용자 목록에서 슈퍼관리자가 보이지 않음
+- [ ] 삭제된 사용자 복원 가능
+- [ ] 삭제된 부서/메뉴/게시판/테넌트/프로젝트 복원 가능
+- [ ] 영구삭제 버튼이 보이지 않음
+- [ ] 슈퍼관리자 수정/삭제 불가 (API 레벨)
+
+### 4.2 슈퍼관리자 테스트
+- [ ] 모든 사용자 조회 가능 (슈퍼관리자 포함)
+- [ ] 삭제된 항목 복원 가능
+- [ ] 영구삭제 가능
+- [ ] 다른 슈퍼관리자 관리 가능
+
+---
+
+## 5. 이슈 해결
+
+### 5.1 302 Found 에러
+**문제**: 일반관리자가 복원 API 호출 시 302 리다이렉트 발생
+**원인**: restore 라우트가 `super.admin` 미들웨어 내부에 있었음
+**해결**: restore 라우트를 미들웨어 밖으로 이동
+
+### 5.2 Soft-deleted 사용자 권한 체크 실패
+**문제**: `User::find()`가 soft-deleted 사용자를 조회하지 못함
+**원인**: Eloquent 기본 동작으로 soft-deleted 레코드 제외
+**해결**: `User::withTrashed()->find()` 사용
+
+---
+
+## 6. 관련 문서
+
+- `claudedocs/archive-restore-feature-analysis.md` - 아카이브/복원 기능 분석
+- `CURRENT_WORKS.md` - 작업 히스토리
\ No newline at end of file
diff --git a/guides/명함추출로직.md b/guides/명함추출로직.md
new file mode 100644
index 0000000..8c7c4db
--- /dev/null
+++ b/guides/명함추출로직.md
@@ -0,0 +1,367 @@
+# 명함 OCR 추출 로직 기술 문서
+
+## 개요
+
+명함 이미지를 업로드하면 Google Gemini Vision API를 통해 자동으로 정보를 추출하여 영업권 등록 폼에 자동 입력하는 시스템입니다.
+
+---
+
+## 시스템 아키텍처
+
+```
+┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
+│ 클라이언트 │ │ MNG 서버 │ │ Gemini API │
+│ (Blade View) │ │ (Laravel) │ │ (Google) │
+└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
+ │ │ │
+ │ 1. 이미지 업로드 │ │
+ │ (Base64) │ │
+ ├──────────────────────>│ │
+ │ │ 2. Vision API 호출 │
+ │ ├──────────────────────>│
+ │ │ │
+ │ │ 3. JSON 응답 │
+ │ │<──────────────────────┤
+ │ 4. 추출 데이터 반환 │ │
+ │<──────────────────────┤ │
+ │ │ │
+ │ 5. 폼 필드 자동 입력 │ │
+ │ │ │
+```
+
+---
+
+## 파일 구조
+
+```
+/home/aweso/sam/mng/
+├── app/
+│ ├── Http/Controllers/
+│ │ ├── Api/
+│ │ │ └── BusinessCardOcrController.php # OCR API 엔드포인트
+│ │ └── System/
+│ │ └── AiConfigController.php # AI 설정 관리
+│ ├── Models/System/
+│ │ └── AiConfig.php # AI API 설정 모델
+│ └── Services/
+│ └── BusinessCardOcrService.php # Gemini Vision API 호출 서비스
+├── resources/views/
+│ ├── sales/prospects/
+│ │ └── create.blade.php # 영업권 등록 (드래그앤드롭 UI)
+│ └── system/ai-config/
+│ └── index.blade.php # AI 설정 관리 페이지
+└── routes/
+ └── web.php # 라우트 정의
+
+/home/aweso/sam/api/
+└── database/migrations/
+ └── 2026_01_27_100000_create_ai_configs_table.php # AI 설정 테이블
+```
+
+---
+
+## 데이터베이스 스키마
+
+### ai_configs 테이블
+
+| 컬럼 | 타입 | 설명 |
+|------|------|------|
+| id | BIGINT | PK |
+| name | VARCHAR(50) | 설정 이름 |
+| provider | VARCHAR(30) | 제공자 (gemini, claude, openai) |
+| api_key | VARCHAR(255) | API 키 (암호화 저장 권장) |
+| model | VARCHAR(100) | 모델명 (예: gemini-2.0-flash) |
+| base_url | VARCHAR(255) | API Base URL (NULL이면 기본값 사용) |
+| description | TEXT | 설명 |
+| is_active | BOOLEAN | 활성화 여부 (provider당 1개만 활성) |
+| options | JSON | 추가 옵션 |
+| created_at | TIMESTAMP | 생성일시 |
+| updated_at | TIMESTAMP | 수정일시 |
+| deleted_at | TIMESTAMP | 삭제일시 (소프트삭제) |
+
+---
+
+## API 엔드포인트
+
+### POST /api/business-card-ocr
+
+명함 이미지에서 정보를 추출합니다.
+
+**Request:**
+```json
+{
+ "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
+}
+```
+
+**Response (성공):**
+```json
+{
+ "ok": true,
+ "data": {
+ "company_name": "주식회사 샘플",
+ "ceo_name": "홍길동",
+ "business_number": "123-45-67890",
+ "contact_phone": "02-1234-5678",
+ "contact_email": "hong@sample.com",
+ "address": "서울시 강남구 테헤란로 123",
+ "position": "대표이사",
+ "department": "경영지원팀"
+ },
+ "raw_response": "{...}"
+}
+```
+
+**Response (실패):**
+```json
+{
+ "ok": false,
+ "error": "Gemini API 설정이 없습니다."
+}
+```
+
+---
+
+## 핵심 로직
+
+### 1. BusinessCardOcrService.php
+
+```php
+class BusinessCardOcrService
+{
+ public function extractFromImage(string $base64Image): array
+ {
+ // 1. 활성화된 Gemini 설정 조회
+ $config = AiConfig::getActiveGemini();
+
+ // 2. Gemini Vision API 호출
+ return $this->callGeminiVisionApi($config, $base64Image);
+ }
+
+ private function callGeminiVisionApi(AiConfig $config, string $base64Image): array
+ {
+ // API URL 구성
+ $url = "{$config->base_url}/models/{$config->model}:generateContent?key={$config->api_key}";
+
+ // Base64 이미지 데이터 처리
+ // data:image/jpeg;base64, 접두사 제거
+
+ // API 호출
+ $response = Http::timeout(30)->post($url, [
+ 'contents' => [[
+ 'parts' => [
+ ['inline_data' => ['mime_type' => $mimeType, 'data' => $imageData]],
+ ['text' => $prompt]
+ ]
+ ]],
+ 'generationConfig' => [
+ 'temperature' => 0.1,
+ 'responseMimeType' => 'application/json'
+ ]
+ ]);
+
+ // 응답 파싱 및 정규화
+ return $this->normalizeData($parsed);
+ }
+}
+```
+
+### 2. Gemini Vision API 프롬프트
+
+```
+이 명함 이미지에서 다음 정보를 추출해주세요.
+
+## 추출 항목
+1. company_name: 회사명/상호
+2. ceo_name: 대표자명/담당자명
+3. business_number: 사업자등록번호 (000-00-00000 형식)
+4. contact_phone: 연락처/전화번호
+5. contact_email: 이메일
+6. address: 주소
+7. position: 직책
+8. department: 부서
+
+## 규칙
+1. 정보가 없으면 빈 문자열("")로 응답
+2. 사업자번호는 10자리 숫자를 000-00-00000 형식으로 변환
+3. 전화번호는 하이픈 포함 형식 유지
+4. 한국어로 된 정보를 우선 추출
+
+## 출력 형식 (JSON)
+{
+ "company_name": "",
+ "ceo_name": "",
+ "business_number": "",
+ ...
+}
+```
+
+### 3. 데이터 정규화
+
+```php
+private function normalizeData(array $data): array
+{
+ // 사업자번호 정규화 (10자리 → 000-00-00000)
+ if (!empty($data['business_number'])) {
+ $digits = preg_replace('/\D/', '', $data['business_number']);
+ if (strlen($digits) === 10) {
+ $data['business_number'] = substr($digits, 0, 3) . '-'
+ . substr($digits, 3, 2) . '-'
+ . substr($digits, 5);
+ }
+ }
+
+ return [
+ 'company_name' => trim($data['company_name'] ?? ''),
+ 'ceo_name' => trim($data['ceo_name'] ?? ''),
+ // ... 기타 필드
+ ];
+}
+```
+
+---
+
+## 프론트엔드 (create.blade.php)
+
+### 드래그앤드롭 영역
+
+```html
+
+
명함 이미지를 드래그하거나 클릭하여 업로드
+
+
+
+
+
+```
+
+### JavaScript 처리 로직
+
+```javascript
+// 파일 처리
+async function handleFile(file) {
+ // 1. 이미지 미리보기
+ const reader = new FileReader();
+ reader.onload = async (e) => {
+ // 미리보기 표시
+ document.getElementById('ocr-preview-image').src = e.target.result;
+
+ // 2. OCR API 호출
+ showOcrLoading(true);
+ const response = await fetch('/api/business-card-ocr', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
+ body: JSON.stringify({ image: e.target.result })
+ });
+
+ const result = await response.json();
+
+ // 3. 폼 필드 자동 입력
+ if (result.ok) {
+ fillFormFields(result.data);
+ }
+ };
+ reader.readAsDataURL(file);
+}
+
+// 폼 필드 자동 입력 (하이라이트 효과 포함)
+function fillFormFields(data) {
+ const fieldMap = {
+ 'company_name': 'name',
+ 'ceo_name': 'ceo_name',
+ 'business_number': 'business_number',
+ // ...
+ };
+
+ for (const [key, fieldName] of Object.entries(fieldMap)) {
+ if (data[key]) {
+ const input = document.querySelector(`[name="${fieldName}"]`);
+ if (input) {
+ input.value = data[key];
+ // 하이라이트 효과
+ input.classList.add('bg-yellow-100');
+ setTimeout(() => input.classList.remove('bg-yellow-100'), 2000);
+ }
+ }
+ }
+}
+```
+
+---
+
+## AI 설정 관리
+
+### 라우트
+
+```php
+// routes/web.php
+Route::prefix('system')->name('system.')->group(function () {
+ Route::get('ai-config', [AiConfigController::class, 'index'])->name('ai-config.index');
+ Route::post('ai-config', [AiConfigController::class, 'store'])->name('ai-config.store');
+ Route::put('ai-config/{id}', [AiConfigController::class, 'update'])->name('ai-config.update');
+ Route::delete('ai-config/{id}', [AiConfigController::class, 'destroy'])->name('ai-config.destroy');
+ Route::post('ai-config/{id}/toggle', [AiConfigController::class, 'toggle'])->name('ai-config.toggle');
+ Route::post('ai-config/test', [AiConfigController::class, 'test'])->name('ai-config.test');
+});
+
+Route::post('api/business-card-ocr', [BusinessCardOcrController::class, 'process']);
+```
+
+### Provider별 기본 설정
+
+```php
+// AiConfig.php
+public const DEFAULT_BASE_URLS = [
+ 'gemini' => 'https://generativelanguage.googleapis.com/v1beta',
+ 'claude' => 'https://api.anthropic.com/v1',
+ 'openai' => 'https://api.openai.com/v1',
+];
+
+public const DEFAULT_MODELS = [
+ 'gemini' => 'gemini-2.0-flash',
+ 'claude' => 'claude-sonnet-4-20250514',
+ 'openai' => 'gpt-4o',
+];
+```
+
+---
+
+## 에러 처리
+
+| 상황 | 에러 메시지 | 대응 |
+|------|------------|------|
+| Gemini 설정 없음 | "Gemini API 설정이 없습니다" | AI 설정 페이지에서 설정 추가 |
+| API 호출 실패 | "AI API 호출 실패: {status}" | API 키/모델 확인 |
+| 연결 실패 | "AI API 연결 실패" | 네트워크/Base URL 확인 |
+| 응답 파싱 실패 | "AI 응답 파싱 실패" | 프롬프트 조정 필요 |
+| Rate Limit | 429 에러 | 잠시 후 재시도 |
+
+---
+
+## 보안 고려사항
+
+1. **API 키 보호**: `api_key` 컬럼 암호화 저장 권장
+2. **마스킹**: UI에서 API 키 앞 8자리만 표시
+3. **CSRF 보호**: 모든 POST 요청에 CSRF 토큰 포함
+4. **파일 검증**: 이미지 파일만 허용 (accept="image/*")
+
+---
+
+## 향후 개선 사항
+
+1. **Claude/OpenAI Vision 지원**: 현재 Gemini만 지원, 타 provider 확장 가능
+2. **배치 처리**: 여러 명함 동시 처리
+3. **OCR 결과 캐싱**: 동일 이미지 재처리 방지
+4. **API 키 암호화**: Laravel Crypt 활용
+
+---
+
+## 참고 자료
+
+- [Gemini API 문서](https://ai.google.dev/gemini-api/docs)
+- [Gemini Vision API](https://ai.google.dev/gemini-api/docs/vision)
+- API 키 파일 위치: `/home/aweso/sam/sales/apikey/gemini_api_key.txt`
+
+---
+
+*문서 작성일: 2026-01-27*
diff --git a/guides/모달창_생성시_유의사항.md b/guides/모달창_생성시_유의사항.md
new file mode 100644
index 0000000..fa56c08
--- /dev/null
+++ b/guides/모달창_생성시_유의사항.md
@@ -0,0 +1,233 @@
+# 모달창 생성 시 유의사항
+
+## 개요
+
+이 문서는 SAM 프로젝트에서 모달창을 구현할 때 발생할 수 있는 문제점과 해결 방법을 정리한 것입니다.
+
+---
+
+## 1. pointer-events 문제
+
+### 문제 상황
+
+모달 배경 클릭을 방지하면서 모달 내부만 클릭 가능하게 하려고 다음과 같은 구조를 사용했을 때:
+
+```html
+
+
+
+
+
+
+
+
+
+```
+
+**증상**: 모달은 표시되지만 내부의 버튼, 입력 필드 등 모든 요소가 클릭되지 않음 (마치 돌덩어리처럼 동작)
+
+### 원인
+
+- `pointer-events-none`이 부모에 있고 `pointer-events-auto`가 자식에 있는 구조
+- AJAX로 로드된 내용이 `pointer-events-auto` div 안에 들어가도, 그 안의 요소들에 pointer-events가 제대로 상속되지 않을 수 있음
+- 특히 동적으로 로드된 HTML에서 이 문제가 자주 발생
+
+### 해결 방법
+
+`pointer-events-none/auto` 구조를 사용하지 않고 단순화:
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## 2. AJAX로 로드된 HTML에서 함수 호출 문제
+
+### 문제 상황
+
+```html
+
+
+```
+
+**증상**: `closeModal is not defined` 오류 발생
+
+### 원인
+
+- 함수가 `function closeModal() {}` 형태로 정의되면 호이스팅되지만, 모듈 스코프나 블록 스코프 안에 있을 수 있음
+- AJAX로 로드된 HTML에서 전역 함수에 접근하지 못할 수 있음
+
+### 해결 방법
+
+**방법 1: window 객체에 명시적 등록**
+
+```javascript
+// 전역 스코프에 함수 등록
+window.closeModal = function() {
+ document.getElementById('modal').classList.add('hidden');
+ document.body.style.overflow = '';
+};
+```
+
+**방법 2: 이벤트 델리게이션 (권장)**
+
+```html
+
+
+```
+
+```javascript
+// JavaScript: document 레벨에서 이벤트 감지
+document.addEventListener('click', function(e) {
+ const closeBtn = e.target.closest('[data-close-modal]');
+ if (closeBtn) {
+ e.preventDefault();
+ window.closeModal();
+ }
+});
+```
+
+---
+
+## 3. 배경 스크롤 방지
+
+### 모달 열 때
+
+```javascript
+document.body.style.overflow = 'hidden';
+```
+
+### 모달 닫을 때
+
+```javascript
+document.body.style.overflow = '';
+```
+
+---
+
+## 4. ESC 키로 모달 닫기
+
+```javascript
+document.addEventListener('keydown', function(e) {
+ if (e.key === 'Escape') {
+ window.closeModal();
+ }
+});
+```
+
+---
+
+## 5. 완전한 모달 구현 예시
+
+### HTML 구조
+
+```html
+
+