diff --git a/projects/e-sign/technical-design.md b/projects/e-sign/technical-design.md index 6066846..6405a6d 100644 --- a/projects/e-sign/technical-design.md +++ b/projects/e-sign/technical-design.md @@ -2,7 +2,7 @@ > **프로젝트명**: SAM E-Sign (가칭) > **작성일**: 2026-02-12 -> **버전**: v1.0 (구현 완료) +> **버전**: v1.1 (필드 템플릿 & 복사 기능 추가) > **작성자**: DX 추진팀 --- @@ -243,6 +243,8 @@ esign_contracts (1) ──── (N) esign_signers │ │ (N) (N) esign_sign_fields esign_audit_logs + +esign_field_templates (1) ──── (N) esign_field_template_items ``` ### 4.2 esign_contracts (계약서) @@ -406,6 +408,56 @@ CREATE TABLE esign_audit_logs ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` +### 4.6 esign_field_templates (필드 배치 템플릿) + +```sql +CREATE TABLE esign_field_templates ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + name VARCHAR(100) NOT NULL, -- 템플릿 이름 + description TEXT NULL, -- 템플릿 설명 + signer_count TINYINT UNSIGNED DEFAULT 2, -- 서명자 수 + is_active BOOLEAN DEFAULT TRUE, -- 활성 여부 (삭제 시 false) + created_by BIGINT UNSIGNED NULL, -- 생성자 user_id + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE, + FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_esign_field_templates_tenant (tenant_id), + INDEX idx_esign_field_templates_active (is_active) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +### 4.7 esign_field_template_items (템플릿 필드 항목) + +```sql +CREATE TABLE esign_field_template_items ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + template_id BIGINT UNSIGNED NOT NULL, + signer_order TINYINT UNSIGNED NOT NULL, -- 서명자 순서 (1, 2, ...) + page_number SMALLINT UNSIGNED NOT NULL, -- 페이지 번호 + position_x DECIMAL(8,2) NOT NULL, -- X 좌표 (%) + position_y DECIMAL(8,2) NOT NULL, -- Y 좌표 (%) + width DECIMAL(8,2) NOT NULL, -- 너비 (%) + height DECIMAL(8,2) NOT NULL, -- 높이 (%) + field_type ENUM('signature','stamp','text','date','checkbox') DEFAULT 'signature', + field_label VARCHAR(100) NULL, -- 필드 라벨 + is_required BOOLEAN DEFAULT TRUE, + sort_order SMALLINT UNSIGNED DEFAULT 0, + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + FOREIGN KEY (template_id) REFERENCES esign_field_templates(id) ON DELETE CASCADE, + INDEX idx_esign_field_template_items_template (template_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +> **핵심 설계**: `signer_id` 대신 `signer_order`(1, 2)를 저장합니다. +> 템플릿 적용 시 현재 계약 서명자의 `sign_order`와 매핑하여 `signer_id`를 결정합니다. + --- ## 5. API 명세 @@ -563,7 +615,91 @@ Content-Type: application/json GET /api/v1/esign/contracts/{id}/fields ``` -### 5.3 서명 요청 API +### 5.3 필드 템플릿 API + +#### 템플릿 목록 조회 +``` +GET /esign/contracts/templates?signer_count=2 +``` + +**Response 200:** +```json +{ + "success": true, + "data": [ + { + "id": 1, + "name": "기본 2인 서명 배치", + "description": "마지막 페이지 좌우 서명란", + "signer_count": 2, + "items": [ + { + "signer_order": 1, "page_number": 3, + "position_x": 15.5, "position_y": 82.0, + "width": 20.0, "height": 8.0, + "field_type": "signature", "field_label": "갑 서명" + } + ] + } + ] +} +``` + +#### 템플릿 저장 (현재 필드를 템플릿으로) +``` +POST /esign/contracts/templates +Content-Type: application/json +``` + +```json +{ + "name": "기본 2인 서명 배치", + "description": "마지막 페이지 좌우 서명란", + "items": [ + { + "signer_order": 1, "page_number": 3, + "position_x": 15.5, "position_y": 82.0, + "width": 20.0, "height": 8.0, + "field_type": "signature", "field_label": "갑 서명", + "is_required": true + } + ] +} +``` + +> `signer_order`는 프론트엔드에서 `signer_id` → 해당 서명자의 `sign_order`로 변환하여 전송합니다. + +#### 템플릿 삭제 (soft delete) +``` +DELETE /esign/contracts/templates/{id} +``` +> `is_active`를 `false`로 변경합니다. + +#### 템플릿을 계약에 적용 +``` +POST /esign/contracts/{id}/apply-template +Content-Type: application/json +``` + +```json +{ + "template_id": 1 +} +``` + +> 기존 필드를 삭제하고 템플릿의 필드를 적용합니다. +> `signer_order` → 현재 계약의 `sign_order`에 해당하는 `signer_id`로 매핑합니다. +> 템플릿의 `signer_count`가 계약의 서명자 수보다 크면 422 에러를 반환합니다. + +#### 다른 계약에서 필드 복사 +``` +POST /esign/contracts/{id}/copy-fields/{sourceId} +``` + +> 소스 계약의 필드를 대상 계약으로 복사합니다. +> 소스 서명자의 `sign_order` → 대상 서명자의 `sign_order`로 매핑합니다. + +### 5.4 서명 요청 API #### 서명 요청 발송 ``` @@ -576,7 +712,7 @@ POST /api/v1/esign/contracts/{id}/send POST /api/v1/esign/contracts/{id}/remind ``` -### 5.4 서명 수행 API (토큰 기반, 비로그인) +### 5.5 서명 수행 API (토큰 기반, 비로그인) #### 서명 페이지 접속 ``` @@ -663,7 +799,7 @@ POST /api/v1/esign/sign/{access_token}/reject } ``` -### 5.5 문서 API +### 5.6 문서 API #### 원본 PDF 조회 (인증 후) ``` @@ -694,7 +830,7 @@ GET /api/v1/esign/contracts/{id}/verify } ``` -### 5.6 감사 로그 API +### 5.7 감사 로그 API #### 감사 로그 조회 ``` @@ -858,7 +994,7 @@ storage/app/esign/ |---|--------|--------|------|------| | 1 | ES_DASH | 대시보드 | /esign | 계약 현황 통계 + 목록 | | 2 | ES_CREATE | 계약 생성 | /esign/create | PDF 업로드 + 정보 입력 | -| 3 | ES_FIELDS | 서명 위치 지정 | /esign/{id}/fields | PDF 위에 서명란 배치 | +| 3 | ES_FIELDS | 서명 위치 지정 | /esign/{id}/fields | PDF 위에 서명란 배치 + 템플릿 저장/불러오기/복사 | | 4 | ES_SEND | 서명 요청 발송 | /esign/{id}/send | 상대방 정보 입력 + 발송 | | 5 | ES_DETAIL | 계약 상세 | /esign/{id} | 진행 상태 + 감사 로그 | @@ -908,6 +1044,17 @@ storage/app/esign/ | 계약 목록 필터/정렬/검색 | MNG + API | | 서명 거절 + 사유 입력 | MNG + API | +### Phase 3.5: 필드 템플릿 & 복사 (구현 완료) + +| 작업 | 담당 | 상태 | +|------|------|------| +| esign_field_templates / esign_field_template_items 테이블 생성 | API | 완료 | +| EsignFieldTemplate, EsignFieldTemplateItem 모델 | MNG | 완료 | +| 템플릿 CRUD API (목록/저장/삭제) | MNG | 완료 | +| 템플릿 적용 API (signer_order 매핑) | MNG | 완료 | +| 다른 계약에서 필드 복사 API | MNG | 완료 | +| 서명 위치 설정 화면에 템플릿 드롭다운 + 모달 3개 | MNG | 완료 | + ### Phase 4: 확장 기능 (v2, 추후) | 기능 | 설명 | @@ -915,7 +1062,6 @@ storage/app/esign/ | SMS 인증 | Coolsms/NHN Cloud 연동 | | 카카오 알림톡 | 카카오 비즈메시지 연동 | | 다자간 서명 (3인 이상) | signers 테이블 확장 | -| 템플릿 관리 | 자주 쓰는 계약서 양식 저장 | | 외부 API 제공 | 타 시스템에서 전자계약 호출 | | 블록체인 공증 | 계약 해시를 블록체인에 기록 | @@ -954,7 +1100,9 @@ database/migrations/ ├── 2026_02_12_100000_create_esign_contracts_table.php ├── 2026_02_12_110000_create_esign_signers_table.php ├── 2026_02_12_120000_create_esign_sign_fields_table.php -└── 2026_02_12_130000_create_esign_audit_logs_table.php +├── 2026_02_12_130000_create_esign_audit_logs_table.php +├── 2026_02_12_140000_create_esign_field_templates_table.php +└── 2026_02_12_140100_create_esign_field_template_items_table.php app/Models/ESign/ ├── EsignContract.php @@ -988,8 +1136,17 @@ routes/api/v1/ ### 10.2 MNG 프로젝트 (`/home/aweso/sam/mng`) ``` +app/Models/ESign/ +├── EsignContract.php +├── EsignSigner.php +├── EsignSignField.php +├── EsignAuditLog.php +├── EsignFieldTemplate.php # 필드 템플릿 +└── EsignFieldTemplateItem.php # 템플릿 필드 항목 + app/Http/Controllers/ESign/ ├── EsignController.php # 인증 필요 (5 화면) +├── EsignApiController.php # 내부 API (9 메서드 + 템플릿 5 메서드) └── EsignPublicController.php # 비인증 (3 화면) resources/views/esign/ @@ -1089,6 +1246,13 @@ EsignSignField EsignAuditLog ├── contract() → BelongsTo → EsignContract └── signer() → BelongsTo → EsignSigner + +EsignFieldTemplate +├── items() → HasMany → EsignFieldTemplateItem +└── creator() → BelongsTo → User + +EsignFieldTemplateItem +└── template() → BelongsTo → EsignFieldTemplate ``` --- @@ -1312,7 +1476,89 @@ public function dashboard(Request $request): View|Response --- -## 16. 미구현 기능 (v1.1 이후) +## 16. 필드 템플릿 사용법 (사용자 가이드) + +### 16.1 개요 + +서명 필드를 매 계약마다 수동 배치하는 반복 작업을 줄이기 위한 기능입니다. +자주 쓰는 필드 배치를 **템플릿으로 저장**하거나, **기존 계약에서 복사**할 수 있습니다. + +### 16.2 진입 경로 + +``` +사이드바: 전자계약(E-Sign) → 대시보드 (또는 보관함) + → 계약 클릭 → 상세 페이지 + → [서명 위치 설정] 버튼 → 필드 편집기 진입 +``` + +### 16.3 Toolbar 메뉴 + +필드 편집기 상단 Toolbar 우측에 **[템플릿 ▾]** 드롭다운 버튼이 있습니다. + +``` +[← 뒤로] [−] 100% [+] [▦] [↩ ↪] [템플릿 ▾] [저장] + ├─ 📁 템플릿으로 저장 + ├─ 📂 템플릿 불러오기 + └─ 📋 다른 계약에서 복사 +``` + +### 16.4 시나리오별 사용법 + +#### A. 템플릿으로 저장 (반복 사용할 배치 저장) + +1. 계약의 서명 위치 설정 화면에서 필드를 원하는 대로 배치합니다 +2. **[템플릿 ▾]** → **📁 템플릿으로 저장** 클릭 +3. 모달에서 **이름**과 **설명**(선택)을 입력합니다 +4. **[저장]** 클릭 → 현재 필드 배치가 템플릿으로 저장됩니다 + +> 저장 시 각 필드의 `signer_id`는 자동으로 `signer_order`(1, 2)로 변환됩니다. +> 따라서 다른 계약에 적용해도 서명자 순서에 맞게 자동 매핑됩니다. + +#### B. 템플릿 불러오기 (저장된 배치를 새 계약에 적용) + +1. 새 계약의 서명 위치 설정 화면 진입 +2. **[템플릿 ▾]** → **📂 템플릿 불러오기** 클릭 +3. 모달에 저장된 템플릿 목록이 표시됩니다 (현재 계약의 서명자 수에 맞는 것만) +4. 원하는 템플릿 선택 → **[적용]** 클릭 +5. 확인 대화상자에서 **확인** → 기존 필드가 삭제되고 템플릿 필드가 적용됩니다 + +> 템플릿의 서명자 수가 현재 계약보다 많으면 에러 메시지가 표시됩니다. +> 불필요한 템플릿은 목록에서 **[×]** 버튼으로 삭제할 수 있습니다. + +#### C. 다른 계약에서 복사 (템플릿 없이 직접 복사) + +1. 새 계약의 서명 위치 설정 화면 진입 +2. **[템플릿 ▾]** → **📋 다른 계약에서 복사** 클릭 +3. 모달에서 계약 **제목 또는 코드로 검색** +4. 복사할 계약 선택 → **[복사]** 클릭 +5. 확인 대화상자에서 **확인** → 필드가 복사됩니다 + +> 소스 계약 서명자의 `sign_order`를 기준으로 대상 계약 서명자에 매핑됩니다. +> 현재 계약 자신은 목록에서 제외됩니다. + +### 16.5 서명자 매핑 로직 + +``` +[템플릿/복사 적용 시] +signer_order = 1 → 현재 계약에서 sign_order = 1인 서명자의 signer_id +signer_order = 2 → 현재 계약에서 sign_order = 2인 서명자의 signer_id + +[예시] +템플릿: signer_order=1 (갑 서명란), signer_order=2 (을 서명란) +계약 A: 김갑순(sign_order=1, id=10), 박을동(sign_order=2, id=11) +결과: signer_order=1 → signer_id=10, signer_order=2 → signer_id=11 +``` + +### 16.6 주의사항 + +- 템플릿/복사 적용 시 **기존 필드가 모두 삭제**됩니다 (확인 대화상자 표시) +- 적용 후 **[저장] 버튼을 눌러야** DB에 최종 반영됩니다 +- 적용 후 필드 위치를 추가 조정할 수 있습니다 +- Undo(Ctrl+Z)로 적용 전 상태로 되돌릴 수 없습니다 (서버에서 직접 적용되므로) + +--- + +## 17. 미구현 기능 (v1.1 이후) | 기능 | 현재 상태 | 구현 방안 | |------|----------|----------| @@ -1326,4 +1572,4 @@ public function dashboard(Request $request): View|Response --- -*이 문서는 SAM E-Sign v1.0 구현 기준 기술 설계서입니다. 최종 업데이트: 2026-02-12* +*이 문서는 SAM E-Sign v1.1 구현 기준 기술 설계서입니다. 최종 업데이트: 2026-02-12*