docs: [종합정비] Phase 1 시스템 현황 문서 14개 작성

- system/overview.md: 전체 아키텍처 개요
- system/api-structure.md: API 구조 (220 모델, 1027 엔드포인트, 18 라우트 도메인)
- system/react-structure.md: React 구조 (249 페이지, 612 컴포넌트)
- system/mng-structure.md: MNG 구조 (171 컨트롤러, 436 Blade 뷰)
- system/docker-setup.md: Docker 7 컨테이너 구성
- system/database/README.md + 9개 도메인 스키마 (270+ 테이블)
  - core, hr, sales, production, finance, boards, files, system, erp-analysis

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 18:03:13 +09:00
parent 0ace50b006
commit d4e5f62413
30 changed files with 7589 additions and 0 deletions

208
system/api-structure.md Normal file
View File

@@ -0,0 +1,208 @@
# API 서버 구조 현황
> **최종 갱신**: 2026-02-27
> **기술 스택**: Laravel 12 + PHP 8.4 + Sanctum + Spatie Permission + L5-Swagger
---
## 1. 프로젝트 규모
| 항목 | 수량 |
|------|------|
| API 엔드포인트 | ~1,027 |
| 컨트롤러 | 131 |
| 모델 | 220 |
| 서비스 | 180 |
| FormRequest | 271 |
| 미들웨어 | 9 |
| Swagger 문서 | 110 |
| 마이그레이션 | 459 |
| 설정 파일 | 22 |
---
## 2. 디렉토리 구조
```
app/
├── Http/
│ ├── Controllers/Api/
│ │ ├── Admin/ 글로벌 메뉴 관리
│ │ └── V1/ 메인 API (131개)
│ │ ├── Admin/ FCM 관리
│ │ ├── Audit/ 감사 로그
│ │ ├── Construction/ 시공 (작업지시, 결과, 설정)
│ │ ├── Design/ 설계 (모델, 버전, BOM 템플릿)
│ │ ├── Documents/ 문서 (템플릿, 생성)
│ │ ├── ESign/ 전자서명
│ │ ├── ItemMaster/ 품목 마스터 (9개)
│ │ └── [104 Root Controllers]
│ ├── Middleware/ 9개
│ └── Requests/ 271개 (FormRequest)
├── Models/ 220개 (32개 도메인)
├── Services/ 180개
├── Swagger/v1/ 110개 (OpenAPI 문서)
├── Helpers/ 4개
├── Observers/ 5개
├── Traits/ 모델/감사 트레이트
└── Exceptions/ 2개
```
---
## 3. 라우트 도메인별 엔드포인트
| 라우트 파일 | 엔드포인트 | 주요 기능 |
|------------|-----------|----------|
| finance.php | 180 | 재무 (결제, 청구서, 세금계산서, 급여) |
| hr.php | 141 | 인사 (급여, 근태, 휴가, 대출) |
| common.php | 128 | 공통 (코드, 분류, 카테고리, 필드) |
| sales.php | 122 | 영업 (수주, 견적, 거래처, 출하) |
| boards.php | 84 | 게시판 (동적 필드 지원) |
| inventory.php | 79 | 재고 (자재, 입고, 창고) |
| production.php | 67 | 생산 (작업지시, 작업실적, 공정) |
| design.php | 61 | 설계 (모델, 버전, BOM 템플릿) |
| users.php | 34 | 사용자 (프로필, 역할, 초대) |
| admin.php | 29 | 관리자 (테넌트, 메뉴, FCM) |
| tenants.php | 23 | 테넌트 (전환, 프로필) |
| files.php | 20 | 파일 (업로드, 저장, 조회) |
| esign.php | 18 | 전자서명 워크플로우 |
| documents.php | 17 | 문서 (템플릿, 생성) |
| auth.php | 9 | 인증 (로그인, 로그아웃, 등록) |
| audit.php | 7 | 감사 (로그, 이력) |
| stats.php | 5 | 통계/리포트 |
| app.php | 3 | 앱 버전, 상태 체크 |
| **합계** | **~1,027** | |
```
routes/
├── api.php 메인 API 진입점
└── api/v1/
├── admin.php
├── app.php
├── audit.php
├── auth.php
├── boards.php
├── common.php
├── design.php
├── documents.php
├── esign.php
├── files.php
├── finance.php
├── hr.php
├── inventory.php
├── production.php
├── sales.php
├── stats.php
├── tenants.php
└── users.php
```
---
## 4. 미들웨어
| 미들웨어 | 역할 |
|---------|------|
| ApiKeyMiddleware | API 키 인증 + Sanctum 토큰 검증 |
| ApiRateLimiter | API 속도 제한 |
| ApiVersionMiddleware | API 버전 라우팅 |
| CheckPermission | 권한 기반 접근 제어 |
| CheckSwaggerAuth | Swagger UI 인증 |
| CorsMiddleware | CORS 정책 |
| LogApiRequest | API 요청/응답 로깅 |
| PermMapper | 권한 매핑 변환 |
| SetAuditSessionVariables | 감사 세션 컨텍스트 |
---
## 5. 아키텍처 패턴
### 인증 체계
- **글로벌**: API Key (모든 요청)
- **사용자**: Sanctum Bearer Token (Access 120분, Refresh 7일)
- **내부**: HMAC (`INTERNAL_EXCHANGE_SECRET`, mng ↔ api)
### 응답 포맷
```json
{
"success": true,
"message": "message.key.from.lang",
"data": { ... }
}
```
- `ApiResponse::handle()` 헬퍼로 통일
### 핵심 패턴
- **Service-First**: 비즈니스 로직 → Service 클래스
- **FormRequest 필수**: Controller에서 직접 검증 금지
- **BelongsToTenant**: 모든 비즈니스 모델에 tenant_id 격리
- **Auditable**: created_by, updated_by, deleted_by 자동 기록
- **SoftDeletes**: 대부분의 엔티티 논리 삭제
### 헬퍼
| 헬퍼 | 역할 |
|------|------|
| ApiResponse | JSON 응답 포맷 통일 |
| ItemTypeHelper | 품목 분류 로직 |
| Legacy5130Calculator | 레거시 5130 연동 계산 |
| TenantCodeGenerator | 테넌트별 코드 생성 |
---
## 6. 주요 의존성
| 패키지 | 버전 | 용도 |
|--------|------|------|
| laravel/framework | ^12.0 | 코어 프레임워크 |
| laravel/sanctum | ^4.0 | API 토큰 인증 |
| spatie/laravel-permission | ^6.21 | RBAC 권한 관리 |
| darkaonline/l5-swagger | ^9.0 | Swagger/OpenAPI 문서 |
| maatwebsite/excel | ^3.1 | Excel 가져오기/내보내기 |
| google/auth | ^1.49 | Google 인증 |
| doctrine/dbal | ^4.3 | DB 추상화 |
---
## 7. 설정
```
config/
├── app.php 앱 메타데이터
├── audit.php 감사 로깅 (13개월 보존)
├── auth.php 인증 (Sanctum, API 키)
├── authz.php 인가 + 권한
├── cors.php CORS 정책
├── custom.php 커스텀 앱 설정
├── database.php DB 연결 (samdb, sam_stat)
├── fcm.php Firebase Cloud Messaging
├── l5-swagger.php Swagger 설정
├── pagination.php 페이지네이션 기본값
├── permission.php Spatie Permission
├── products.php 제품별 설정
├── sanctum.php Sanctum 토큰 설정
└── [기타 8개]
```
---
## 8. api ↔ mng ↔ react 관계
```
react (dev.sam.kr) ──Server Actions──→ api (api.sam.kr) ←──DB 공유──→ mng (mng.sam.kr)
MySQL (samdb)
```
- **api**: DB 마이그레이션 유일 관리자 (459개). 모든 테이블 정의
- **mng**: 자체 모델 (185개), 마이그레이션 없음. 동일 DB 직접 접근
- **react**: Server Actions로 api 호출. DB 직접 접근 없음
- **내부 통신**: HMAC 기반 `INTERNAL_EXCHANGE_SECRET` (mng → api)
- **파일 공유**: mng가 api의 `storage/app/tenants` 마운트
### Swagger 문서
- 110개 OpenAPI 문서 파일
- 도메인별 태그 구성
- 보안 스키마: ApiKeyAuth + BearerAuth (Sanctum)
- URL: `api.sam.kr/api/documentation`

523
system/board-system-spec.md Normal file
View File

@@ -0,0 +1,523 @@
# 게시판 시스템 스펙
**작성일**: 2025-11-27
**상태**: 설계 완료, 구현 대기
**관련 프로젝트**: mng, api
---
## 1. 개요
### 1.1 목적
- mng에서 **시스템 게시판**을 생성하여 모든 테넌트에게 제공
- sam(api)에서 테넌트별 **자체 게시판** 생성 가능
- 공지사항, 1:1 문의, FAQ 등 다양한 게시판 유형 지원
### 1.2 핵심 개념
| 구분 | 설명 |
|------|------|
| **시스템 게시판** | mng에서 생성, `is_system=true`, 모든 테넌트에서 접근 가능 |
| **테넌트 게시판** | sam에서 생성, `is_system=false`, 해당 테넌트만 접근 |
| **board_type** | 자유 입력 (notice, qna, faq, free 등 제한 없음) |
### 1.3 역할 분리
```
┌─────────────────────────────────────────────────────────────┐
│ mng (상위 관리자) │
├─────────────────────────────────────────────────────────────┤
│ • 시스템 게시판 CRUD (is_system = true) │
│ • 게시판 필드 정의 (board_settings) │
│ • 게시판 유형 자유 설정 (board_type) │
│ • tenant_id = NULL │
└─────────────────────────────────────────────────────────────┘
↓ 전체 테넌트 공개
┌─────────────────────────────────────────────────────────────┐
│ sam (api - 테넌트용) │
├─────────────────────────────────────────────────────────────┤
│ • 테넌트 게시판 CRUD (is_system = false) │
│ • 시스템 게시판 사용 (읽기 전용 구조) │
│ • 게시글 CRUD (posts) │
│ • tenant_id = 현재 테넌트 │
└─────────────────────────────────────────────────────────────┘
```
---
## 2. 데이터베이스 스키마
### 2.1 기존 테이블 현황
현재 구현된 테이블 (6개):
- `boards` - 게시판 정의
- `board_settings` - EAV 커스텀 필드 정의
- `posts` - 게시글
- `post_custom_field_values` - EAV 커스텀 필드 값
- `board_comments` - 댓글
- `board_files` - 파일 첨부
### 2.2 boards 테이블 확장
**추가 컬럼:**
| 컬럼 | 타입 | 설명 |
|------|------|------|
| `is_system` | TINYINT(1) DEFAULT 0 | 시스템 게시판 여부 (1=전체 공개) |
| `board_type` | VARCHAR(50) NULL | 게시판 유형 (자유 입력) |
**변경 컬럼:**
| 컬럼 | 변경 내용 |
|------|----------|
| `tenant_id` | NOT NULL → **NULL 허용** (시스템 게시판용) |
**최종 boards 테이블 구조:**
```sql
CREATE TABLE boards (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NULL COMMENT '테넌트 ID (NULL=시스템 게시판)',
is_system TINYINT(1) DEFAULT 0 COMMENT '시스템 게시판 여부 (1=전체 테넌트 공개)',
board_type VARCHAR(50) NULL COMMENT '게시판 유형 (notice, qna, faq, free 등)',
board_code VARCHAR(50) NOT NULL COMMENT '게시판 코드 (URL용)',
name VARCHAR(100) NOT NULL COMMENT '게시판명',
description TEXT NULL COMMENT '게시판 설명',
editor_type VARCHAR(20) DEFAULT 'wysiwyg' COMMENT '에디터 타입',
allow_files TINYINT(1) DEFAULT 1 COMMENT '파일 첨부 허용',
max_file_count INT DEFAULT 5 COMMENT '최대 파일 수',
max_file_size INT DEFAULT 20480 COMMENT '최대 파일 크기 (KB)',
extra_settings JSON NULL COMMENT '추가 설정 (권한, 옵션 등)',
is_active TINYINT(1) DEFAULT 1 COMMENT '활성 여부',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
created_by BIGINT UNSIGNED NULL,
updated_by BIGINT UNSIGNED NULL,
deleted_at TIMESTAMP NULL,
deleted_by BIGINT UNSIGNED NULL,
INDEX idx_boards_tenant (tenant_id),
INDEX idx_boards_is_system (is_system),
INDEX idx_boards_board_type (board_type),
UNIQUE INDEX uk_boards_code (tenant_id, board_code)
);
```
### 2.3 board_settings 테이블 (EAV 필드 정의)
현재 구조 유지:
```sql
CREATE TABLE board_settings (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
board_id BIGINT UNSIGNED NOT NULL COMMENT '게시판 ID',
name VARCHAR(100) NOT NULL COMMENT '필드명',
field_key VARCHAR(50) NOT NULL COMMENT '필드 키',
field_type VARCHAR(30) NOT NULL COMMENT '필드 타입 (text, number, select, date 등)',
field_meta JSON NULL COMMENT '필드 메타 (옵션, 유효성 등)',
is_required TINYINT(1) DEFAULT 0 COMMENT '필수 여부',
sort_order INT DEFAULT 0 COMMENT '정렬 순서',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
created_by BIGINT UNSIGNED NULL,
updated_by BIGINT UNSIGNED NULL,
INDEX idx_board_settings_board (board_id),
FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE
);
```
### 2.4 posts 테이블
현재 구조 유지 (SoftDeletes 적용):
```sql
CREATE TABLE posts (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
board_id BIGINT UNSIGNED NOT NULL COMMENT '게시판 ID',
user_id BIGINT UNSIGNED NOT NULL COMMENT '작성자 ID',
title VARCHAR(255) NOT NULL COMMENT '제목',
content LONGTEXT NULL COMMENT '내용',
editor_type VARCHAR(20) DEFAULT 'wysiwyg' COMMENT '에디터 타입',
ip_address VARCHAR(45) NULL COMMENT 'IP 주소',
is_notice TINYINT(1) DEFAULT 0 COMMENT '공지 여부',
is_secret TINYINT(1) DEFAULT 0 COMMENT '비밀글 여부',
views INT DEFAULT 0 COMMENT '조회수',
status VARCHAR(20) DEFAULT 'active' COMMENT '상태',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
created_by BIGINT UNSIGNED NULL,
updated_by BIGINT UNSIGNED NULL,
deleted_at TIMESTAMP NULL,
deleted_by BIGINT UNSIGNED NULL,
INDEX idx_posts_tenant_board (tenant_id, board_id),
INDEX idx_posts_status (status),
FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE
);
```
### 2.5 post_custom_field_values 테이블 (EAV 값)
현재 구조 유지:
```sql
CREATE TABLE post_custom_field_values (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
post_id BIGINT UNSIGNED NOT NULL COMMENT '게시글 ID',
field_id BIGINT UNSIGNED NOT NULL COMMENT '필드 ID (board_settings.id)',
value TEXT NULL COMMENT '필드 값',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
created_by BIGINT UNSIGNED NULL,
updated_by BIGINT UNSIGNED NULL,
INDEX idx_pcfv_post (post_id),
INDEX idx_pcfv_field (field_id),
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
FOREIGN KEY (field_id) REFERENCES board_settings(id) ON DELETE CASCADE
);
```
---
## 3. 게시판 유형 (board_type)
### 3.1 권장 유형
| board_type | 설명 | 특징 |
|------------|------|------|
| `notice` | 공지사항 | 관리자만 작성, 전체 공개 |
| `qna` | 1:1 문의 | 작성자+관리자만 열람, 비밀글 기본 |
| `faq` | FAQ | 관리자 작성, 카테고리 분류 |
| `free` | 자유게시판 | 댓글 허용, 전체 공개 |
| `gallery` | 갤러리 | 이미지 중심 |
| `download` | 자료실 | 파일 첨부 중심 |
### 3.2 유형별 extra_settings 예시
```json
// notice (공지사항)
{
"write_roles": ["admin", "manager"],
"allow_comment": false,
"allow_secret": false
}
// qna (1:1 문의)
{
"write_roles": ["*"],
"allow_comment": true,
"default_secret": true,
"only_author_view": true
}
// faq
{
"write_roles": ["admin"],
"use_category": true,
"allow_comment": false
}
// free (자유게시판)
{
"write_roles": ["*"],
"allow_comment": true,
"allow_secret": true
}
```
---
## 4. API 설계
### 4.1 mng API (시스템 게시판 관리)
```
# 시스템 게시판 CRUD
GET /mng/boards # 시스템 게시판 목록
POST /mng/boards # 시스템 게시판 생성
GET /mng/boards/{id} # 시스템 게시판 상세
PUT /mng/boards/{id} # 시스템 게시판 수정
DELETE /mng/boards/{id} # 시스템 게시판 삭제
# 게시판 필드 관리
GET /mng/boards/{id}/fields # 필드 목록
POST /mng/boards/{id}/fields # 필드 추가
PUT /mng/boards/{id}/fields/{fid} # 필드 수정
DELETE /mng/boards/{id}/fields/{fid} # 필드 삭제
POST /mng/boards/{id}/fields/reorder # 필드 순서 변경
```
### 4.2 sam API (테넌트용)
```
# 게시판 조회 (시스템 + 테넌트)
GET /v1/boards # 접근 가능한 게시판 목록
GET /v1/boards/{code} # 게시판 상세
# 테넌트 게시판 관리
POST /v1/boards # 테넌트 게시판 생성
PUT /v1/boards/{code} # 테넌트 게시판 수정
DELETE /v1/boards/{code} # 테넌트 게시판 삭제
# 게시글 CRUD
GET /v1/boards/{code}/posts # 게시글 목록
POST /v1/boards/{code}/posts # 게시글 작성
GET /v1/boards/{code}/posts/{id} # 게시글 상세
PUT /v1/boards/{code}/posts/{id} # 게시글 수정
DELETE /v1/boards/{code}/posts/{id} # 게시글 삭제
# 댓글
GET /v1/posts/{id}/comments # 댓글 목록
POST /v1/posts/{id}/comments # 댓글 작성
PUT /v1/comments/{id} # 댓글 수정
DELETE /v1/comments/{id} # 댓글 삭제
```
---
## 5. 모델 설계
### 5.1 Board 모델
```php
// api/app/Models/Boards/Board.php
namespace App\Models\Boards;
use App\Traits\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Board extends Model
{
use SoftDeletes;
protected $fillable = [
'tenant_id', 'is_system', 'board_type', 'board_code', 'name',
'description', 'editor_type', 'allow_files', 'max_file_count',
'max_file_size', 'extra_settings', 'is_active',
'created_by', 'updated_by', 'deleted_by',
];
protected $casts = [
'is_system' => 'boolean',
'is_active' => 'boolean',
'allow_files' => 'boolean',
'extra_settings' => 'array',
];
/**
* 현재 테넌트에서 접근 가능한 게시판
* - 시스템 게시판 (is_system = true)
* - 해당 테넌트 게시판 (tenant_id = 현재 테넌트)
*/
public function scopeAccessible($query, int $tenantId)
{
return $query->where(function ($q) use ($tenantId) {
$q->where('is_system', true)
->orWhere('tenant_id', $tenantId);
})->where('is_active', true);
}
/**
* 시스템 게시판만 (mng용)
*/
public function scopeSystemOnly($query)
{
return $query->where('is_system', true);
}
/**
* 테넌트 게시판만
*/
public function scopeTenantOnly($query, int $tenantId)
{
return $query->where('is_system', false)
->where('tenant_id', $tenantId);
}
// 관계
public function fields()
{
return $this->hasMany(BoardSetting::class, 'board_id')
->orderBy('sort_order');
}
public function posts()
{
return $this->hasMany(Post::class, 'board_id');
}
// Helper
public function getSetting(string $key, $default = null)
{
return data_get($this->extra_settings, $key, $default);
}
}
```
### 5.2 BoardService
```php
// api/app/Services/Boards/BoardService.php
namespace App\Services\Boards;
use App\Models\Boards\Board;
use App\Services\Service;
class BoardService extends Service
{
/**
* 접근 가능한 게시판 목록 (시스템 + 테넌트)
*/
public function getAccessibleBoards(array $filters = [])
{
return Board::accessible($this->tenantId())
->when(isset($filters['board_type']), fn($q) =>
$q->where('board_type', $filters['board_type']))
->orderBy('is_system', 'desc')
->orderBy('name')
->get();
}
/**
* 시스템 게시판 목록 (mng용)
*/
public function getSystemBoards()
{
return Board::systemOnly()
->orderBy('name')
->get();
}
/**
* 시스템 게시판 생성 (mng용)
*/
public function createSystemBoard(array $data): Board
{
$data['is_system'] = true;
$data['tenant_id'] = null;
$data['created_by'] = $this->apiUserId();
return Board::create($data);
}
/**
* 테넌트 게시판 생성 (sam용)
*/
public function createTenantBoard(array $data): Board
{
$data['is_system'] = false;
$data['tenant_id'] = $this->tenantId();
$data['created_by'] = $this->apiUserId();
return Board::create($data);
}
/**
* 게시판 코드로 조회
*/
public function getBoardByCode(string $code): ?Board
{
return Board::accessible($this->tenantId())
->where('board_code', $code)
->first();
}
}
```
---
## 6. 권한 모델
### 6.1 extra_settings 기반 권한
게시판별 권한은 `extra_settings` JSON 필드로 관리:
```json
{
"permissions": {
"read": ["*"], // 모든 역할 읽기 가능
"write": ["admin", "user"], // admin, user 역할만 작성
"manage": ["admin"] // admin만 관리
}
}
```
### 6.2 권한 검증
```php
// Board 모델에 추가
public function canRead(User $user): bool
{
$roles = $this->getSetting('permissions.read', ['*']);
return in_array('*', $roles) ||
$user->hasAnyRole($roles);
}
public function canWrite(User $user): bool
{
$roles = $this->getSetting('permissions.write', ['*']);
return in_array('*', $roles) ||
$user->hasAnyRole($roles);
}
public function canManage(User $user): bool
{
$roles = $this->getSetting('permissions.manage', ['admin']);
return $user->hasAnyRole($roles);
}
```
---
## 7. 구현 계획
### 7.1 Phase 1: DB 마이그레이션 (api/)
1. `boards` 테이블 확장
- `tenant_id` nullable 변경
- `is_system` 컬럼 추가
- `board_type` 컬럼 추가
- `deleted_at`, `deleted_by` 추가 (없으면)
- 인덱스 추가
### 7.2 Phase 2: 모델/서비스 수정 (api/)
1. `Board` 모델 업데이트
- 스코프 추가 (accessible, systemOnly, tenantOnly)
- SoftDeletes 적용
- 권한 헬퍼 메서드
2. `BoardService` 구현
- 시스템/테넌트 게시판 분리 로직
### 7.3 Phase 3: mng 화면 개발
1. 게시판 목록 (`/mng/boards`)
2. 게시판 생성/수정 폼
3. 게시판 필드 관리
### 7.4 Phase 4: sam API 개발
1. 게시판 API (`/v1/boards`)
2. 게시글 API (`/v1/boards/{code}/posts`)
3. Swagger 문서
---
## 8. 관련 문서
- [데이터베이스 스키마](./database/README.md)
- [API 개발 규칙](../standards/api-rules.md)
- [MNG Critical Rules](../../mng/docs/MNG_CRITICAL_RULES.md)
---
**최종 업데이트**: 2025-11-27
**버전**: 1.0
**상태**: 설계 완료

193
system/database/README.md Normal file
View File

@@ -0,0 +1,193 @@
# 데이터베이스 스키마 현황
> **최종 갱신**: 2026-02-27
> **DB**: MySQL 8.0 (samdb + sam_stat)
> **마이그레이션**: 459개 (api/에서만 관리)
---
## 1. 규모 요약
| 항목 | 수량 |
|------|------|
| Eloquent 모델 | 220 |
| 도메인 그룹 | 32 |
| 마이그레이션 | 459 (메인 437 + 통계 22) |
| DB 연결 | 2 (samdb, sam_stat) |
---
## 2. 도메인별 모델 분포
| 도메인 | 모델 수 | 주요 엔티티 | 상세 |
|--------|---------|------------|------|
| Tenants | 56 | Tenant, Department, Approval, Payment, Stock 등 | [tenants.md](tenants.md) |
| Stats | 21 | StatFinanceDaily, DimClient, DimDate 등 | [stats.md](stats.md) |
| Documents | 15 | Document, DocumentTemplate, DocumentApproval 등 | [documents.md](documents.md) |
| Commons | 10 | Category, File, Menu, Tag, Classification | [commons.md](commons.md) |
| Quote | 8 | Quote, QuoteItem, QuoteFormula, QuoteRevision | [sales.md](sales.md) |
| Production | 8 | WorkOrder, WorkOrderItem, WorkResult 등 | [production.md](production.md) |
| Orders | 8 | Order, OrderItem, OrderNode, Client 등 | [sales.md](sales.md) |
| ItemMaster | 8 | ItemField, ItemPage, ItemBomItem, CustomTab 등 | [products.md](products.md) |
| Products | 6 | Product, ProductComponent, Part, Price | [products.md](products.md) |
| Interview | 5 | InterviewTemplate, InterviewSession 등 | [hr.md](hr.md) |
| Construction | 5 | Contract, HandoverReport, StructureReview | [production.md](production.md) |
| Boards | 5 | Board, Post, BoardSetting, PostCustomFieldValue | [commons.md](commons.md) |
| Members | 4 | User, UserTenant, UserRole, UserMenuPermission | [tenants.md](tenants.md) |
| Materials | 4 | Material, MaterialReceipt, MaterialInspection | [production.md](production.md) |
| ESign | 4 | EsignContract, EsignSigner, EsignSignField | [documents.md](documents.md) |
| Design | 4 | DesignModel, ModelVersion, BomTemplate | [products.md](products.md) |
| Qualitys | 3 | Inspection, Lot, LotSale | [production.md](production.md) |
| Permissions | 3 | Permission, Role, PermissionOverride | [tenants.md](tenants.md) |
| Items | 3 | Item, ItemDetail, ItemReceipt | [products.md](products.md) |
| BadDebts | 3 | BadDebt, BadDebtDocument, BadDebtMemo | [finance.md](finance.md) |
| Estimate | 2 | Estimate, EstimateItem | [sales.md](sales.md) |
| Audit | 2 | AuditLog, TriggerAuditLog | [commons.md](commons.md) |
| Root Level | 27 | ApiKey, Process, NumberingSequence 등 | 각 도메인 문서 참조 |
---
## 3. 핵심 엔티티 관계도
### 인증 + 멀티테넌시
```
User
├─ hasMany UserTenant (pivot: is_active, is_default)
│ └─ belongsTo Tenant
├─ hasMany UserRole
└─ hasMany UserMenuPermission
Tenant
├─ hasMany Department (계층 구조, parent_id)
│ └─ belongsToMany User (department_user pivot)
├─ hasMany TenantUserProfile (테넌트별 직원 프로필)
└─ hasMany [모든 tenant-scoped 모델]
```
### 제품 + 설계
```
Product
├─ belongsTo Category (계층 분류)
├─ hasMany ProductComponent (BOM)
├─ hasMany Part, Price
└─ morphMany File
DesignModel
└─ hasMany ModelVersion
└─ hasMany BomTemplate
└─ hasMany BomTemplateItem
```
### 견적 → 수주 → 생산 흐름
```
Quote (견적)
├─ belongsTo Client
├─ hasMany QuoteItem
└─ → Order 변환
Order (수주)
├─ belongsTo Quote, Client
├─ hasMany OrderItem (options JSON)
├─ hasMany OrderNode (설계 분해)
└─ hasMany WorkOrder
WorkOrder (작업지시)
├─ belongsTo Order, Process
├─ hasMany WorkOrderItem (options JSON)
├─ hasMany WorkOrderStepProgress
└─ hasMany WorkResult (작업실적)
```
### 문서 + 전자결재
```
Document
├─ belongsTo DocumentTemplate
├─ morphTo linkable (다형 - Order, Quote 등)
├─ hasMany DocumentApproval (결재 단계)
└─ hasMany DocumentData (동적 폼)
DocumentTemplate
├─ hasMany Section → Field → Item
└─ hasMany ApprovalLine (결재선)
```
---
## 4. 공통 패턴
### 트레이트 사용 현황
| 트레이트 | 적용 모델 수 | 역할 |
|---------|-------------|------|
| BelongsToTenant | ~160 (73%) | tenant_id 자동 스코핑 |
| Auditable | ~150 (68%) | created_by, updated_by, deleted_by |
| SoftDeletes | ~150 (68%) | 논리 삭제 (deleted_at) |
| ModelTrait | ~80 (36%) | 날짜 처리, is_active 스코프 |
| HasRoles (Spatie) | 3 | RBAC 권한 관리 |
| HasApiTokens (Sanctum) | 1 (User) | API 토큰 관리 |
### 표준 컬럼 구조
모든 비즈니스 테이블의 공통 컬럼:
```
id PK
tenant_id FK (멀티테넌시)
created_by FK → users (감사)
updated_by FK → users (감사)
deleted_by FK → users (감사)
created_at timestamp
updated_at timestamp
deleted_at timestamp (논리삭제)
is_active boolean
sort_order integer
options JSON (유연한 속성 저장)
```
### DB 설계 패턴
| 패턴 | 사용 예시 |
|------|----------|
| 계층 구조 (Self-Reference) | Category, Department (parent_id) |
| 다형 관계 (Polymorphic) | File, PermissionOverride (morphable_type/id) |
| EAV (Entity-Attribute-Value) | PostCustomFieldValue, CategoryField |
| JSON 컬럼 | Product.bom, OrderItem.options, WorkOrderItem.options |
| Pivot 테이블 | user_tenants, department_user |
| BOM/Tree | ProductComponent, BomTemplateItem |
| 버전 관리 | ModelVersion, QuoteRevision, OrderVersion |
| 상태 코드 | Order.status (DRAFT/CONFIRMED/IN_PRODUCTION) |
### JSON options 컬럼 정책
> **FK/조인키만** 테이블 컬럼으로 추가.
> **나머지 속성은** `options` 또는 `attributes` JSON에 저장.
> 이유: 멀티테넌시 환경에서 테넌트별 스키마 변경 없이 유연한 관리.
---
## 5. DB 연결
| 연결명 | DB | 용도 |
|--------|-----|------|
| mysql (기본) | samdb | 모든 비즈니스 데이터 |
| sam_stat | sam_stat | 통계/집계 전용 (21 모델) |
| chandj | chandj | 레거시 5130 (마이그레이션 대상) |
---
## 6. 도메인별 상세 문서
| 문서 | 포함 도메인 |
|------|-----------|
| [tenants.md](tenants.md) | Tenants, Members, Permissions |
| [products.md](products.md) | Products, ItemMaster, Items, Design |
| [sales.md](sales.md) | Orders, Quote, Estimate |
| [production.md](production.md) | Production, Construction, Materials, Qualitys |
| [finance.md](finance.md) | Finance (Tenants 하위), BadDebts |
| [hr.md](hr.md) | HR (Tenants 하위), Interview |
| [documents.md](documents.md) | Documents, ESign |
| [commons.md](commons.md) | Commons, Boards, Audit |
| [stats.md](stats.md) | Stats (sam_stat DB) |

View File

@@ -0,0 +1,85 @@
# 공통 / 게시판 / 감사 도메인
> **모델 수**: Commons 10 + Boards 5 + Audit 2 = 17
> **핵심**: 범용 분류, 파일 관리, 게시판 시스템, 감사 로그
---
## 주요 테이블
### 공통 (Commons)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| categories | Category | 범용 계층 분류 (parent_id, self-reference) |
| category_fields | CategoryField | 카테고리 동적 필드 (EAV) |
| category_templates | CategoryTemplate | 카테고리 필드 스냅샷 (버전) |
| category_logs | CategoryLog | 카테고리 변경 로그 |
| classifications | Classification | 분류 체계 |
| files | File | 파일 첨부 (다형: morphMany) |
| menus | Menu | 메뉴 체계 |
| tags | Tag | 태그 시스템 |
| holidays | Holiday | 공휴일 |
| common_codes | CommonCode | 공통 코드 |
### 게시판 (Boards)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| boards | Board | 게시판 정의 |
| posts | Post | 게시글 |
| board_settings | BoardSetting | 게시판 동적 필드 정의 |
| post_custom_field_values | PostCustomFieldValue | 게시글 커스텀 필드 값 (EAV) |
| board_comments | BoardComment | 댓글 |
### 감사 (Audit)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| audit_logs | AuditLog | 변경 감사 로그 (불변) |
| trigger_audit_logs | TriggerAuditLog | DB 트리거 감사 로그 |
---
## 관계 구조
```
Category (계층 분류)
├─ belongsTo Category (parent_id → self)
├─ hasMany Category (children)
├─ hasMany CategoryField (EAV 동적 필드)
├─ hasMany CategoryTemplate (필드 버전 스냅샷)
├─ hasMany Product
└─ belongsToMany File via tags
Board (게시판)
├─ hasMany Post
│ ├─ hasMany PostCustomFieldValue (EAV)
│ └─ hasMany BoardComment
└─ hasMany BoardSetting (동적 필드 정의)
File (범용 첨부)
└─ morphTo fileable (어떤 모델에든 첨부 가능)
```
---
## EAV 패턴
게시판과 카테고리 모두 **EAV(Entity-Attribute-Value)** 패턴 사용:
```
Board → BoardSetting (필드 정의: field_name, field_type)
Post → PostCustomFieldValue (필드 값: board_setting_id, value)
Category → CategoryField (필드 정의: field_name, field_type)
```
---
## 특이사항
- `File`은 다형(morphMany) 관계 — 모든 모델에서 파일 첨부 가능
- `AuditLog`는 불변 (timestamps 없음, SoftDeletes 없음)
- 감사 로그 보존 정책: 13개월
- 게시판 시스템: EAV 패턴으로 동적 필드 지원 (노션 스타일 전환 계획 중)

View File

@@ -0,0 +1,68 @@
# 문서 / 전자서명 도메인
> **모델 수**: Documents 15 + ESign 4 = 19
> **핵심**: 문서 템플릿 기반 생성, 전자결재, 전자서명
---
## 주요 테이블
### 문서 (Documents)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| documents | Document | 문서 마스터 (다형 linkable) |
| document_templates | DocumentTemplate | 문서 템플릿 정의 |
| document_template_sections | DocumentTemplateSection | 템플릿 섹션 |
| document_template_section_fields | DocumentTemplateSectionField | 섹션 필드 |
| document_template_section_items | DocumentTemplateSectionItem | 체크리스트 항목 |
| document_template_field_presets | DocumentTemplateFieldPreset | 필드 프리셋 |
| document_template_approval_lines | DocumentTemplateApprovalLine | 결재선 정의 |
| document_approvals | DocumentApproval | 문서 결재 기록 |
| document_attachments | DocumentAttachment | 문서 첨부 |
| document_data | DocumentData | 문서 동적 데이터 |
| document_links | DocumentLink | 문서 링크 |
### 전자서명 (ESign)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| esign_contracts | EsignContract | 전자서명 계약 |
| esign_signers | EsignSigner | 서명자 |
| esign_sign_fields | EsignSignField | 서명 필드 |
| esign_audit_logs | EsignAuditLog | 전자서명 감사 로그 |
---
## 관계 구조
```
DocumentTemplate
├─ hasMany DocumentTemplateSection
│ └─ hasMany DocumentTemplateSectionField
│ ├─ hasMany DocumentTemplateSectionItem (체크리스트)
│ └─ hasMany DocumentTemplateFieldPreset (프리셋)
└─ hasMany DocumentTemplateApprovalLine (결재선)
Document
├─ belongsTo DocumentTemplate
├─ morphTo linkable (Order, Quote 등에 연결)
├─ hasMany DocumentApproval
├─ hasMany DocumentAttachment
├─ hasMany DocumentData
└─ hasMany DocumentLink
EsignContract
├─ hasMany EsignSigner
├─ hasMany EsignSignField
└─ hasMany EsignAuditLog
```
---
## 특이사항
- 문서 시스템은 템플릿 기반 (Section → Field → Item 3단계)
- `Document.linkable`은 다형 관계 (Order, Quote 등에 첨부 가능)
- 전자서명은 별도 감사 로그 보유 (EsignAuditLog)
- 문서 15개 모델 중 7개가 템플릿 구조 관련

View File

@@ -0,0 +1,57 @@
ㅇ# 재무 / 회계 도메인
> **모델 수**: Finance 관련 (Tenants 하위) + BadDebts 3
> **핵심**: 매입/매출, 결제, 세금계산서, 대출, 경비
> **API 엔드포인트**: 180개 (finance.php — 최대 규모)
---
## 주요 테이블
### 결제 / 청구
| 테이블 | 모델 | 역할 |
|--------|------|------|
| payments | Payment | 결제 마스터 |
| bills | Bill | 청구서 |
| deposits | Deposit | 입금 |
| withdrawals | Withdrawal | 출금 |
| receivables | Receivables | 매출채권 |
| vendor_ledgers | VendorLedger | 거래처 원장 |
### 세금 / 카드
| 테이블 | 모델 | 역할 |
|--------|------|------|
| tax_invoices | TaxInvoice | 세금계산서 |
| vat_* | Vat 관련 | 부가세 관리 |
| cards | Card | 법인카드 |
| card_transactions | CardTransaction | 카드 거래 |
| bank_accounts | BankAccount | 은행 계좌 |
| bank_transactions | BankTransaction | 은행 거래 |
### 대손 (BadDebts)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| bad_debts | BadDebt | 대손 마스터 |
| bad_debt_documents | BadDebtDocument | 대손 관련 문서 |
| bad_debt_memos | BadDebtMemo | 대손 메모 |
### 기타 재무
| 테이블 | 모델 | 역할 |
|--------|------|------|
| expected_expenses | ExpectedExpense | 예상 경비 |
| entertainments | Entertainment | 접대비 |
| purchases | Purchase | 매입 |
| subscriptions | Subscription | 구독/정기결제 |
---
## 특이사항
- 재무 모델은 대부분 `Tenants/` 디렉토리 하위에 위치
- finance.php 라우트가 180개로 전체 API 중 최대 규모
- 바로빌 연동은 mng에서 관리 (api에는 해당 모델 없음)
- 모든 재무 모델은 BelongsToTenant + Auditable + SoftDeletes

68
system/database/hr.md Normal file
View File

@@ -0,0 +1,68 @@
# 인사 / HR 도메인
> **모델 수**: HR 관련 (Tenants 하위) + Interview 5
> **핵심**: 급여, 근태, 휴가, 대출, 면접
> **API 엔드포인트**: 141개 (hr.php)
---
## 주요 테이블
### 급여 / 근무
| 테이블 | 모델 | 역할 |
|--------|------|------|
| payrolls | Payroll | 급여 마스터 |
| salaries | Salary | 급여 항목 |
| attendances | Attendance | 근태 기록 |
| attendance_requests | AttendanceRequest | 근태 요청 |
| leaves | Leave | 휴가 사용 기록 |
| leave_policies | LeavePolicy | 휴가 정책 |
| labors | Labor | 노무비 |
### 대출
| 테이블 | 모델 | 역할 |
|--------|------|------|
| loans | Loan | 직원 대출 |
### 면접 (Interview)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| interview_templates | InterviewTemplate | 면접 양식 |
| interview_sessions | InterviewSession | 면접 세션 |
| interview_questions | InterviewQuestion | 면접 질문 |
| interview_categories | InterviewCategory | 면접 카테고리 |
| interview_responses | InterviewResponse | 면접 답변 |
---
## 관계 구조
```
TenantUserProfile (직원)
├─ hasMany Payroll
├─ hasMany Attendance
├─ hasMany Leave
├─ hasMany Loan
└─ hasMany AttendanceRequest
LeavePolicy
└─ belongsTo Tenant (테넌트별 휴가 정책)
InterviewTemplate
├─ hasMany InterviewCategory
│ └─ hasMany InterviewQuestion
└─ hasMany InterviewSession
└─ hasMany InterviewResponse
```
---
## 특이사항
- HR 모델은 대부분 `Tenants/` 디렉토리 하위
- 직원 정보는 `TenantUserProfile` (테넌트별 프로필)
- 면접 모델은 별도 `Interview/` 도메인으로 분리
- 급여 관련 최근 추가: long_term_care 컬럼 (2026-02-27)

View File

@@ -0,0 +1,112 @@
# 생산 / 시공 / 자재 / 품질 도메인
> **모델 수**: Production 8 + Construction 5 + Materials 4 + Qualitys 3 = 20
> **핵심**: 작업지시, 공정 관리, 자재/LOT 추적, 품질 검사
---
## 주요 테이블
### 생산 (Production)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| work_orders | WorkOrder | 작업지시 마스터 |
| work_order_items | WorkOrderItem | 작업지시 항목 (options JSON) |
| work_order_step_progress | WorkOrderStepProgress | 단계별 진행 추적 |
| work_order_bending_details | WorkOrderBendingDetail | 절곡 상세 사양 |
| work_order_assignees | WorkOrderAssignee | 작업자 배정 |
| work_order_issues | WorkOrderIssue | 작업 이슈 기록 |
| work_results | WorkResult | 작업실적 |
| work_order_material_inputs | WorkOrderMaterialInput | 자재 투입 기록 |
### 시공 (Construction)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| contracts | Contract | 시공 계약 |
| handover_reports | HandoverReport | 인수인계 보고서 |
| handover_report_managers | HandoverReportManager | 인수인계 담당자 |
| structure_reviews | StructureReview | 구조 검토 |
### 자재 (Materials)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| materials | Material | 자재 마스터 |
| material_receipts | MaterialReceipt | 자재 입고 |
| material_inspections | MaterialInspection | 수입검사 마스터 |
| material_inspection_items | MaterialInspectionItem | 수입검사 항목 |
### 품질 (Qualitys)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| inspections | Inspection | 검사 마스터 |
| lots | Lot | LOT 관리 |
| lot_sales | LotSale | LOT 출고 |
---
## 관계 구조
```
WorkOrder (작업지시)
├─ belongsTo Order
├─ belongsTo Process (공정)
├─ belongsTo Department (담당 팀)
├─ hasMany WorkOrderItem
│ ├─ options: JSON { floor, code, width, height, slat_info, bending_info }
│ └─ hasMany WorkOrderMaterialInput
├─ hasMany WorkOrderStepProgress
├─ hasMany WorkOrderBendingDetail
├─ hasMany WorkOrderAssignee
├─ hasMany WorkOrderIssue
└─ hasMany WorkResult
Material
├─ belongsTo Category
├─ hasMany MaterialReceipt
├─ hasMany Lot
└─ morphMany File
Inspection
├─ belongsTo WorkOrder (또는 MaterialReceipt)
└─ hasMany InspectionItem
```
---
## WorkOrderItem options JSON 구조
```json
{
"floor": "1F",
"code": "SL-001",
"width": 1200,
"height": 800,
"cutting_info": { ... },
"slat_info": { "joint_bar": 2, "glass_qty": 10 },
"bending_info": { ... },
"wip_info": { ... }
}
```
- OrderItem.options에서 복사됨 (width 직접 접근 가능)
- 조인트바 자동계산: `createWorkOrders()` 에서 처리
---
## LOT 관리 흐름
```
Material → MaterialReceipt (입고)
MaterialInspection (수입검사)
Lot (LOT 생성)
WorkOrderMaterialInput (투입)
LotSale (출고)
```

View File

@@ -0,0 +1,88 @@
# 제품 / 품목 / 설계 도메인
> **모델 수**: Products 6 + ItemMaster 8 + Items 3 + Design 4 = 21
> **핵심**: 제품 정의, BOM 구조, 품목 마스터, 설계 모델
---
## 주요 테이블
### 제품 (Products)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| products | Product | 제품 마스터 (code, name, product_type) |
| product_components | ProductComponent | BOM 구성 (parent-child 관계) |
| parts | Part | 부품 정의 |
| prices | Price | 가격 정보 |
| common_codes | CommonCode | 공통 코드 |
### 품목 마스터 (ItemMaster)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| item_fields | ItemField | 품목 필드 정의 |
| item_pages | ItemPage | 품목 페이지 구성 |
| item_bom_items | ItemBomItem | 품목 BOM 항목 |
| custom_tabs | CustomTab | 커스텀 탭 |
| unit_options | UnitOption | 단위 옵션 |
### 품목 (Items)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| items | Item | 품목 마스터 |
| item_details | ItemDetail | 품목 상세 |
| item_receipts | ItemReceipt | 품목 입고 |
### 설계 (Design)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| design_models | DesignModel | 설계 모델 마스터 |
| model_versions | ModelVersion | 모델 버전 |
| bom_templates | BomTemplate | BOM 템플릿 |
| bom_template_items | BomTemplateItem | BOM 템플릿 항목 (수량, 로스율) |
---
## 관계 구조
```
Product
├─ belongsTo Category (계층 분류)
├─ hasMany ProductComponent (BOM)
│ └─ child_product_id → Product (자기 참조)
├─ hasMany Part
├─ hasMany Price
└─ morphMany File
Item
├─ hasMany ItemDetail
├─ hasMany ItemReceipt
└─ options JSON: { lot_managed, consumption_method, production_source, input_tracking }
DesignModel → ModelVersion → BomTemplate → BomTemplateItem
```
---
## 품목 options 체계
items.options JSON으로 품목별 관리 방식 정의:
| 속성 | 타입 | 설명 |
|------|------|------|
| lot_managed | bool | LOT 추적 여부 |
| consumption_method | auto/manual/none | 소진 방식 |
| production_source | purchased/self_produced/both | 조달 구분 |
| input_tracking | bool | 원자재 투입 추적 여부 |
### 유형별 조합
| 유형 | 예시 | lot | consumption | source |
|------|------|-----|------------|--------|
| 구매 소모품 (LOT) | 내화실 | true | manual | purchased |
| 구매 소모품 (비LOT) | 장갑, 테이프 | false | manual | purchased |
| 일반 자체생산 | 슬랫, 절곡물 | true | auto | self_produced |
| 잔재 활용 생산 | 조인트바 | true | auto | self_produced |

98
system/database/sales.md Normal file
View File

@@ -0,0 +1,98 @@
# 영업 / 수주 / 견적 도메인
> **모델 수**: Orders 8 + Quote 8 + Estimate 2 = 18
> **핵심**: 견적 → 수주 → 생산 변환 흐름
---
## 주요 테이블
### 수주 (Orders)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| orders | Order | 수주 마스터 (status: DRAFT→CONFIRMED→IN_PRODUCTION) |
| order_items | OrderItem | 수주 항목 (options JSON 포함) |
| order_nodes | OrderNode | 설계 분해 구조 |
| order_item_components | OrderItemComponent | 수주 항목 구성요소 |
| order_histories | OrderHistory | 수주 변경 이력 |
| clients | Client | 거래처 마스터 |
| client_groups | ClientGroup | 거래처 그룹 |
| site_briefings | SiteBriefing | 현장 브리핑 |
### 견적 (Quote)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| quotes | Quote | 견적 마스터 |
| quote_items | QuoteItem | 견적 항목 |
| quote_formulas | QuoteFormula | 견적 공식 |
| quote_formula_categories | QuoteFormulaCategory | 공식 카테고리 |
| quote_revisions | QuoteRevision | 견적 버전 이력 |
### 견적서 (Estimate)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| estimates | Estimate | 견적서 마스터 |
| estimate_items | EstimateItem | 견적서 항목 |
---
## 관계 구조
```
Quote (견적)
├─ belongsTo Client
├─ belongsTo SiteBriefing
├─ belongsTo Item
├─ hasMany QuoteItem
├─ hasMany QuoteRevision
└─ → Order 변환 (OrderService)
Order (수주)
├─ belongsTo Quote
├─ belongsTo Client
├─ hasMany OrderItem
│ ├─ belongsTo Item
│ ├─ hasMany OrderItemComponent
│ └─ options: JSON { floor, code, width, height, cutting_info, slat_info, bending_info }
├─ hasMany OrderNode
├─ hasMany OrderHistory
└─ hasMany WorkOrder (생산으로 변환)
```
---
## 비즈니스 흐름
```
견적(Quote) → 수주(Order) → 작업지시(WorkOrder) → 작업실적(WorkResult)
│ │ │
QuoteItem OrderItem WorkOrderItem
```
### 상태 흐름 (Order)
```
DRAFT → CONFIRMED → IN_PRODUCTION → COMPLETED → SHIPPED
```
---
## OrderItem options JSON 구조
```json
{
"floor": "1F",
"code": "SL-001",
"width": 1200,
"height": 800,
"cutting_info": { ... },
"slat_info": { "joint_bar": 2, "glass_qty": 10 },
"bending_info": { ... },
"wip_info": { ... }
}
```
- 견적 → 수주 변환 시 `extractSlatInfoFromBom()`으로 slat_info 자동 계산
- 조인트바 수량 공식: `(2 + floor((제작가로 - 500) / 1000)) × 셔터수량`

68
system/database/stats.md Normal file
View File

@@ -0,0 +1,68 @@
# 통계 도메인
> **모델 수**: 21
> **DB 연결**: sam_stat (별도 데이터베이스)
> **핵심**: 일별/월별 집계, 차원 테이블, 통계 서비스 15개
---
## 주요 테이블
### 일별 집계 (Daily)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| stat_finance_daily | StatFinanceDaily | 일별 재무 통계 |
| stat_production_daily | StatProductionDaily | 일별 생산 통계 |
| stat_sales_daily | StatSalesDaily | 일별 영업 통계 |
| stat_hr_daily | StatHrDaily | 일별 인사 통계 |
| stat_inventory_daily | StatInventoryDaily | 일별 재고 통계 |
### 월별 집계 (Monthly)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| stat_finance_monthly | StatFinanceMonthly | 월별 재무 통계 |
| stat_production_monthly | StatProductionMonthly | 월별 생산 통계 |
| stat_sales_monthly | StatSalesMonthly | 월별 영업 통계 |
### 차원 테이블 (Dimensions)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| dim_clients | DimClient | 거래처 차원 |
| dim_dates | DimDate | 날짜 차원 |
| dim_products | DimProduct | 제품 차원 |
| dim_departments | DimDepartment | 부서 차원 |
### 기타
| 테이블 | 모델 | 역할 |
|--------|------|------|
| base_stat_model | BaseStatModel | 통계 모델 베이스 |
| stat_* | 기타 통계 모델 | 도메인별 집계 |
---
## 아키텍처
```
[samdb] ──Observer 이벤트──→ [통계 서비스 15개] ──집계──→ [sam_stat DB]
StatEventObserver
├─ 주문 이벤트 → StatSalesDaily 업데이트
├─ 생산 이벤트 → StatProductionDaily 업데이트
├─ 재무 이벤트 → StatFinanceDaily 업데이트
└─ ... (도메인별)
```
---
## 특이사항
- **별도 DB 연결**: `sam_stat` (samdb와 분리)
- **마이그레이션**: 22개 (별도 관리)
- **서비스**: 15개 전용 서비스 (Stats/ 디렉토리)
- **차원 테이블**: 스타 스키마 기반 (DimClient, DimDate 등)
- **이벤트 기반**: Observer 패턴으로 실시간 집계
- API 엔드포인트: 5개 (stats.php) — 대부분 조회용

View File

@@ -0,0 +1,90 @@
# 테넌트 / 사용자 / 권한 도메인
> **모델 수**: Tenants 56 + Members 4 + Permissions 3 = 63
> **핵심**: 멀티테넌시 기반, 모든 비즈니스 데이터의 기준점
---
## 주요 테이블
### 인증 / 사용자
| 테이블 | 모델 | 역할 |
|--------|------|------|
| users | User | 시스템 계정 (Sanctum, HasRoles) |
| user_tenants | UserTenant | 사용자-테넌트 매핑 (is_active, is_default) |
| user_roles | UserRole | 사용자-역할 매핑 |
| user_menu_permissions | UserMenuPermission | 사용자별 메뉴 권한 |
### 테넌트 핵심
| 테이블 | 모델 | 역할 |
|--------|------|------|
| tenants | Tenant | 조직/회사 마스터 |
| departments | Department | 부서 (계층 구조, parent_id) |
| department_user | (pivot) | 부서-사용자 다대다 |
| tenant_user_profiles | TenantUserProfile | 테넌트별 직원 프로필 |
| positions | Position | 직급/직위 |
### 테넌트 설정
| 테이블 | 모델 | 역할 |
|--------|------|------|
| tenant_settings | TenantSetting | 테넌트별 설정값 |
| tenant_field_settings | TenantFieldSetting | 필드별 설정 |
| tenant_option_groups | TenantOptionGroup | 옵션 그룹 정의 |
| tenant_option_values | TenantOptionValue | 옵션 값 |
| tenant_stat_fields | TenantStatField | 통계 필드 설정 |
### 권한 (Spatie Permission)
| 테이블 | 모델 | 역할 |
|--------|------|------|
| permissions | Permission | 권한 정의 |
| roles | Role | 역할 정의 |
| permission_overrides | PermissionOverride | 권한 오버라이드 (다형) |
---
## 관계 구조
```
User (1)
├─ (N) UserTenant ─→ (1) Tenant
├─ (N) UserRole ─→ (1) Role ─→ (N) Permission
└─ (N) UserMenuPermission
Tenant (1)
├─ (N) Department (parent_id → self)
│ └─ (N:M) User via department_user
├─ (N) TenantUserProfile
│ ├─ → User
│ └─ → Department
├─ (N) TenantSetting
└─ (N) [모든 BelongsToTenant 모델]
```
---
## Tenants 하위 비즈니스 모델 (56개)
Tenants 도메인은 조직 내 다양한 비즈니스 기능을 포함:
- **재무**: Payment, Bill, TaxInvoice, JournalEntry, Deposit, Withdrawal 등
- **인사**: Payroll, Attendance, Leave, LeavePolicy, Salary, Loan 등
- **영업**: Sale, Client, ClientGroup, Shipment, Receivables 등
- **재고**: Stock, StockLot, MaterialInput 등
- **결재**: Approval, ApprovalForm, ApprovalLine 등
- **기타**: Calendar, CalendarSchedule, Notification 등
> 이 모델들의 상세 내용은 각 도메인 문서(finance.md, hr.md, sales.md 등)에서 다룸.
---
## 특이사항
- `User`는 BelongsToTenant가 아님 (시스템 전역)
- `UserTenant`으로 다중 테넌트 소속 지원
- `TenantUserProfile`은 테넌트별 직원 정보 (직급, 입사일 등)
- `Department`는 self-reference 계층 구조 (parent_id)
- 권한은 메뉴 기반 RBAC (Spatie Permission + UserMenuPermission)

232
system/docker-setup.md Normal file
View File

@@ -0,0 +1,232 @@
# Docker 환경 설정
> **최종 갱신**: 2026-02-27
> **Docker Compose**: 7 서비스, `samnet` 브리지 네트워크
---
## 1. 서비스 구성
| 서비스 | 이미지 | 포트 | 역할 |
|--------|--------|------|------|
| nginx | nginx:latest | 80, 443 | 리버스 프록시, SSL 종료 |
| api | php:8.4-fpm (custom) | 9000 | Laravel REST API |
| mng | php:8.4-fpm (custom) | 9000 | Laravel 관리자 패널 |
| react | node:20-alpine | 3000 | Next.js 프론트엔드 |
| design | node:20-alpine | 3002 | Vite 디자인 시스템 |
| php73 | php:7.3-fpm (custom) | 9000 | 레거시 5130 |
| mysql | mysql:8.0 | 3306 | 데이터베이스 |
---
## 2. 도메인 매핑
| 도메인 | 대상 | SSL | 비고 |
|--------|------|-----|------|
| api.sam.kr | api:9000 (FastCGI) | O | 대형 JSON 버퍼링 |
| mng.sam.kr | mng:9000 (FastCGI) | O | 관리자 패널 |
| admin.sam.kr | mng:9000 (FastCGI) | O | mng 별칭 |
| dev.sam.kr | react:3000 (Proxy) | O | HMR WebSocket 지원 |
| design.sam.kr | design:3002 (Proxy) | O | HMR WebSocket 지원 |
| 5130.sam.kr | php73:9000 (FastCGI) | O | 레거시 |
| dev.haisa.kr | host.docker.internal:8888 | X | 외부 프록시 |
---
## 3. 네트워크 구조
```
[호스트]
127.0.0.1:80 ─→ nginx:80
127.0.0.1:443 ─→ nginx:443
127.0.0.1:3306 ─→ mysql:3306
[samnet 내부]
nginx ──FastCGI──→ api:9000
nginx ──FastCGI──→ mng:9000
nginx ──FastCGI──→ php73:9000
nginx ──Proxy────→ react:3000
nginx ──Proxy────→ design:3002
api/mng ─────────→ mysql:3306
```
- 모든 포트는 `127.0.0.1` 바인딩 (외부 접근 차단)
- `samnet` 브리지 네트워크로 컨테이너간 통신
---
## 4. Dockerfile 요약
### api / mng (PHP 8.4-FPM)
```
Base: php:8.4-fpm
Extensions: zip, mysqli, pdo, pdo_mysql, intl
Packages: git, unzip, nginx, supervisor
Composer: 2 (멀티스테이지 빌드)
실행: Supervisor (nginx + php-fpm 동시)
Entrypoint: storage:link, 권한 설정
```
### react (Node 20)
```
Base: node:20-alpine
추가: chromium, font-noto-cjk (Puppeteer 지원)
환경: PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
실행: npm run dev (포트 3000)
```
### design (Node 20)
```
Base: node:20-alpine
실행: npm run dev (포트 3002)
```
### php73 (PHP 7.3-FPM)
```
Base: php:7.3-fpm
Extensions: gd, mbstring
설정: short_open_tag=On, display_errors=On
```
---
## 5. 볼륨 마운팅
### 주요 볼륨
- **db_data** (Named Volume): MySQL 데이터 영속성
- **앱 소스코드**: 호스트 → 컨테이너 직접 마운트
- **API 스토리지**: mng가 api의 storage/app/tenants 공유 마운트
### Nginx 설정
- `docker/nginx/nginx.conf``/etc/nginx/nginx.conf` (RO)
- `docker/nginx/ssl/``/etc/nginx/ssl/` (RO)
### PHP 설정
- `uploads.ini`: file_uploads=On, memory_limit=256M, upload_max=20M, post_max=100M
- `supervisord.conf`: php-fpm + nginx 동시 실행 (nodaemon)
---
## 6. MySQL 설정
### my.cnf
```ini
innodb_buffer_pool_size = 256M
innodb_log_buffer_size = 16M
sort_buffer_size = 4M
tmp_table_size = 64M
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
log_bin_trust_function_creators = 1
```
### 초기화 (init.sql)
```sql
CREATE DATABASE samdb; -- API/MNG용
CREATE DATABASE chandj; -- 레거시 5130용
CREATE USER samuser@'%';
GRANT ALL ON samdb.*, chandj.*;
```
---
## 7. Nginx 보안 필터 (API)
```
차단 패턴:
- 경로 트래버설: \.\.\/|\.\.\\|etc\/passwd
- 민감 파일: \.env|\.git|\.htaccess|\.sql|@fs\/
- 악성 User-Agent: sqlmap, nikto, nmap, masscan, metasploit, nessus
응답: 403 Forbidden
```
### 업로드 제한
| 대상 | 최대 크기 |
|------|----------|
| Nginx (기본) | 100M |
| API | 100M |
| MNG | 210M |
### 정적 자산 캐싱
- 대상: js, css, png, jpg, gif, svg, ico, woff2, ttf
- 만료: 30일, Cache-Control: public
---
## 8. CI/CD (Jenkins)
### 파이프라인 요약
| 저장소 | 트리거 브랜치 | 배포 서버 | 특징 |
|--------|-------------|----------|------|
| api | main, develop | 211.117.60.189 | Release 디렉토리, 자동 마이그레이션, 자동 롤백 |
| react | main, develop | 114.203.209.83 (dev), 211.117.60.189 (stage/prod) | PM2 프로세스 관리 |
| mng | main | 211.117.60.189 | npm build + PHP-FPM 재로드 |
| sales | main | 211.117.60.189 | 정적 사이트 rsync만 |
### 배포 전략 (api/mng)
```
1. releases/{RELEASE_ID} 디렉토리 생성 (타임스탬프)
2. rsync 코드 복사 (.git, .env, storage 제외)
3. composer install + npm install
4. php artisan migrate --force
5. config/route/view 캐시 생성
6. /current symlink → 최신 릴리스
7. PHP-FPM 재로드
8. 이전 릴리스 정리 (최근 6개만 유지)
```
### 롤백: 실패 시 이전 릴리스로 symlink 자동 변경
### 알림: Slack (빌드 시작/성공/실패)
---
## 9. 배포 환경 요약
| 환경 | 도메인 | IP | 배포 방식 |
|------|--------|----|----------|
| 로컬 | *.sam.kr | localhost | Docker Compose |
| 개발 | codebridge-x.com | 114.203.209.83 | Jenkins (react only) |
| 스테이지 | TBD | 211.117.60.189 | Jenkins (api, mng, react) |
| 운영 | TBD | 211.117.60.189 | Jenkins (승인 필요, 비활성) |
---
## 10. 디렉토리 구조
```
docker/
├── docker-compose.yml 전체 서비스 정의
├── .env 프로젝트명 설정
├── nginx/
│ ├── nginx.conf 메인 설정 (7개 virtual host)
│ └── ssl/ SSL 인증서
├── api/
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── supervisord.conf
│ ├── entrypoint.sh
│ └── uploads.ini
├── mng/
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── supervisord.conf
│ ├── entrypoint.sh
│ └── uploads.ini
├── react/
│ ├── Dockerfile
│ └── entrypoint.sh
├── design/
│ ├── Dockerfile
│ └── entrypoint.sh
├── 5130/
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── supervisord.conf
│ ├── entrypoint.sh
│ └── uploads.ini
└── mysql/
├── init.sql
├── my.cnf
└── database.sql
```

View File

@@ -0,0 +1,155 @@
# SAM ERP 스토리보드 분석 개요
> 분석 대상: SAM_ERP_Storyboard_D0.8_251216 (113 슬라이드)
> 분석일: 2025-12-17
> 버전: D0.8
## 1. 문서 개요
### 1.1 버전 히스토리
| 날짜 | 버전 | 내용 |
|------|------|------|
| 2025.12.01 | D0.6 | 프론트 초안 - 인사관리 & 전자결재 작성 |
| 2025.12.01 | D0.7 | 프론트 작성 - 인사관리 & 전자결재 피드백 반영 |
| 2025.12.16 | D0.8 | 회계 & 보고서 작성, GPS 출퇴근, 카드/계좌관리, 보고서 추가 |
### 1.2 슬라이드 구성
- **총 113 슬라이드**
- 공통 UI 가이드 + 8개 주요 기능 모듈
## 2. 메뉴 구조 (GNB/LNB)
```
SAM ERP
├── 대시보드
├── MES 메뉴 (영업관리, 판매관리, 구매관리 등)
├── 인사관리
│ ├── 부서관리
│ ├── 사원관리
│ ├── 근태관리
│ └── 휴가관리
├── 전자결재
│ ├── 기안함
│ ├── 결재함
│ └── 참조함
├── 게시판
├── 회계관리
│ ├── 거래처관리
│ ├── 매출관리
│ ├── 매입관리
│ ├── 입금관리
│ ├── 출금관리
│ ├── 어음관리
│ ├── 거래처원장
│ ├── 일일 일보
│ ├── 지출 예상 내역서
│ ├── 미수금 현황
│ ├── 악성채권 추심관리
│ ├── 입출금 계좌 조회
│ └── 카드 내역 조회
├── 기준정보
│ ├── 직급관리
│ ├── 직책관리
│ ├── 권한관리
│ ├── 근무관리
│ ├── 출퇴근관리
│ ├── 휴가관리
│ ├── 카드관리
│ ├── 계좌관리
│ ├── 팝업관리
│ ├── 게시판관리
│ ├── 일반설정
│ └── 알림설정
├── 보고서 및 분석
├── 계정정보
├── 회사정보
├── 구독관리
├── 결제내역
└── 고객센터
```
## 3. 기능 그룹별 슬라이드 매핑
| 그룹 | 슬라이드 범위 | 주요 내용 | 분석 문서 |
|------|---------------|----------|----------|
| 공통 | 3-13 | UI 컴포넌트, 인터랙션, 알림 | [01-common.md](./01-common.md) |
| 인증/영업 | 14-22 | 가입, 로그인, 약관동의 | [02-auth.md](./02-auth.md) |
| GPS 출퇴근 | 23-27 | 모바일 출퇴근 | [03-gps-attendance.md](./03-gps-attendance.md) |
| 인사관리 | 28-46 | 부서/사원/근태/휴가 | [04-hr-management.md](./04-hr-management.md) |
| 전자결재 | 47-59 | 기안/결재/참조함 | [05-approval.md](./05-approval.md) |
| 회계관리 | 60-91 | 거래처/매출/매입/입출금 | [06-accounting.md](./06-accounting.md) |
| 기준정보 | 92-104 | 직급/직책/휴가/카드/계좌 | [07-master-data.md](./07-master-data.md) |
| 보고서 | 105-113 | 분석/AI 리포트 | [08-reports.md](./08-reports.md) |
## 4. 사용자 역할
### 4.1 영업사원 (Sales)
- 운영 로그인
- 회사 등록 신청
- 테넌트 추가 알림
### 4.2 관리자 (Admin)
- 자료 확인
- 가입 승인/거절
- 이메일로 URL 발송
### 4.3 고객사 (Customer/Tenant)
- 약관 동의
- 비밀번호 설정
- SAM 로그인
- 테넌트 추가
## 5. 핵심 비즈니스 플로우
### 5.1 가입 및 로그인 플로우 (슬라이드 15)
```
영업사원: 운영 로그인 → 사업자등록번호 입력 → 회사정보 등록 → 가입신청 완료
↓ (거절)
관리자: 자료 확인 → 승인? → 이메일로 URL 발송 거절 알림
고객사: 약관 동의 → 비밀번호 설정 → SAM 로그인 → 테넌트 추가?
매니저: 테넌트 추가 알림 → 사업자등록번호 입력
```
### 5.2 회계관리 플로우 (슬라이드 61)
```
매출: 거래처 선택 → 매출 등록 → 세금계산서 발행
입금: 입금 등록 → 전액 입금? → 어음 수취?
매입: 거래처 선택 → 매입 등록 → 세금계산서 수취
출금: 출금 등록 → 전액 출금? → 어음 발행?
추심: 미수금 현황 → 연체? → 악성주심? → 악성 추심
조회: 입출금 계좌 조회, 카드 내역 조회
장부/보고서: 거래처원장, 지출 예상 내역서, 일일 일보
```
## 6. 기술 스펙
### 6.1 반응형 웹 브레이크포인트
- 모바일: < 640px (기본)
- 태블릿: 768px ~ 1023px (md)
- 데스크탑: 1024px+ (lg)
- 대형 모니터: 1280px+ (xl)
### 6.2 인터랙션
| 타입 | 적용 |
|------|------|
| Tap | Yes |
| Touch & Hold | No |
| Double Tap | No |
| Drag & Drop | Yes |
| Scroll Up/Down | Yes |
| Swipe Left/Right | Yes |
| Pinch Zoom In/Out | Yes |
### 6.3 알림 타입
- Alert (알림): 사용자에게 상황 알림
- Confirm Alert (확인): 확인/취소 선택
- Toast Message: 2-3초 Fade out
## 7. 다음 단계
1. 기능 그룹별 상세 분석 문서 작성
2. API 엔드포인트 도출
3. 데이터 모델 설계
4. 개발 우선순위 결정

View File

@@ -0,0 +1,143 @@
# 공통 UI 컴포넌트 (슬라이드 3-13)
## 1. 화면 템플릿 (슬라이드 6)
### 1.1 모바일 화면 구조
```
┌─────────────────────────────┐
│ A. Status bar (OS 관리) │
├─────────────────────────────┤
│ B. Browser 영역 │
├──────┬──────────────────────┤
│Button│ Title │Button│ C. Title 영역
├──────┴──────────────────────┤
│ │
│ D. Content 영역 │
│ │
├─────────────────────────────┤
│ E. Browser bar 영역 │
├─────────────────────────────┤
│ F. Keypad 영역 (입력 시) │
└─────────────────────────────┘
```
### 1.2 영역 설명
- **A. Status bar**: 안테나, 통화, 배터리 등 시스템 OS 관리 영역
- **B. Browser 영역**: 브라우저 기능 영역
- **C. Title 영역**: 텍스트 또는 기능 버튼으로 구현, 텍스트 기본 가운데 정렬
- **D. Content 영역**: 컨텐츠 내용 표시, 컨텐츠 길이가 길어질 경우 스크롤 제공
- **E. Browser bar 영역**: 브라우저 유틸 바 영역
- **F. Keypad 영역**: 키보드 입력할 때 활성화, 모든 페이지 위에 덮어쓰기 구현
## 2. GNB/LNB/푸터 (슬라이드 8)
### 2.1 알림 버튼
- 클릭: 알림 팝업 표시
### 2.2 개인 정보 버튼
- 등록: 디폴트 이미지, 이름, 직급
- 클릭: 마이페이지 팝업 표시
### 2.3 회사 로고
- 회사정보 화면에서 등록한 로고 표시
- 회사 변경 선택 시 해당 로고 변경
### 2.4 메뉴 영역
- 메뉴 클릭:
- 하위 메뉴가 있을 경우: 하위 메뉴 하단에 표시
- 하위 메뉴 없을 경우: 해당 메뉴 화면으로 이동
- 목록 길 경우 해당 영역 내 스크롤 처리
### 2.5 MES 메뉴 영역
- 영업관리, 판매관리, 구매관리 등 해당하는 MES 메뉴 영역 표시
### 2.6 푸터 영역
- 모든 화면 하단 공통 표시
## 3. 알림 팝업 (슬라이드 9)
### 3.1 알림 목록
- 항목: 디폴트 원형일, 종류(공지사항), 안내/제목/내용, 전송일시 표시
- 클릭: 해당 상세 화면으로 이동
- 최신순 10개까지 표시
### 3.2 New 아이콘
- 새 알림일 경우 New 아이콘 표시
- 해당 알림 클릭 시 사라짐
### 3.3 빨간 점 아이콘
- 새 알림이 있을 경우 표시
- 해당 알림 모두 클릭 시 사라짐
## 4. 마이페이지 팝업 (슬라이드 10)
### 4.1 계정 아이디 (이메일) 표시
### 4.2 회사 셀렉트 박스
- 종류: 회사명, 회사명... (해당 계정이 생성한 회사(테넌트) 목록 표시)
- 정렬: 등록순
- 한 회사만 소유중일 경우에는 해당 영역 숨김
### 4.3 로그아웃 버튼
- 클릭: "정말 로그아웃하시겠습니까?" 로그아웃 확인 Alert 표시
- 확인 버튼 클릭 시 로그아웃 처리
## 5. 셀렉트 박스 (슬라이드 11)
### 5.1 기본 셀렉트 박스
- 클릭: 하단에 종류 목록 표시
### 5.2 종류 목록
- 목록 중 하나만 선택 가능
### 5.3 다중 선택 셀렉트 박스
- 선택된 첫번째 항목명 + 추가 수 표시
- 텍스트 영역 부족할 경우 '항목..+3' 형태로 표시
### 5.4 다중 선택 종류 목록
- 목록 중 복수 선택 가능
- 전체 선택 시 전체 선택/해제 토글
### 5.5 검색 영역
- 검색어 입력 후 엔터 또는 검색 아이콘 클릭 시 (5-1) 형태로 표시되며 (5-2) 영역에 검색 결과 표시
### 5.6 삭제 버튼
- 클릭: 검색어 삭제 처리, 전체 종류 목록 표시
## 6. 입력 필드 가이드 메시지 (슬라이드 12)
### 6.1 가이드 메시지 표시 위치
- 상황에 따라 입력 필드 하단 또는 Alert에 표시
### 6.2 가이드 메시지 색상
- 긍정일 경우: 녹색
- (1-1) 부정일 경우: 붉은색
## 7. 공지 팝업 (슬라이드 13)
### 7.1 대상
- 전체, 설정 부서
- 내용: 설정 기간동안 대상에게 팝업 표시
### 7.2 팝업 내용 영역
- 이미지, 텍스트
### 7.3 "1일간 이 창을 열지 않음" 체크박스
- 클릭: 체크 설정/해제 토글
- 디폴트: 체크 해제 상태
- 체크 설정 시 1일 동안 팝업 미표시 (차정 기준)
---
## API 도출
### 공통 API
```
GET /api/notifications # 알림 목록 조회
POST /api/notifications/{id}/read # 알림 읽음 처리
GET /api/user/profile # 마이페이지 정보
GET /api/user/companies # 사용자 소속 회사 목록
POST /api/auth/logout # 로그아웃
GET /api/announcements # 공지 팝업 조회
POST /api/announcements/{id}/dismiss # 공지 팝업 닫기 (1일간 안보기)
```

View File

@@ -0,0 +1,205 @@
# 인증/영업 (슬라이드 14-22)
## 1. 가입 및 로그인 플로우차트 (슬라이드 15)
### 1.1 영업사원 플로우
```
운영 로그인 → 사업자등록번호 입력 → 사업자번호 조회?
↓ Yes
회사정보 등록 → 가입신청 완료
↓ (거절)
거절 알림
```
### 1.2 관리자 플로우
```
자료 확인 → 승인? → (Yes) → 이메일로 URL 발송
↓ (No)
계약금50% 결제 확인
```
### 1.3 고객사 플로우
```
약관 동의 → 비밀번호 설정 → SAM 로그인 → 테넌트 추가?
↓ (Yes)
매니저: 테넌트 추가 알림
사업자등록번호 입력
```
## 2. 운영 로그인 (슬라이드 16)
### 2.1 아이디 인풋박스
- 테넌트 생성자일 경우 이메일
- 사용자일 경우 이메일 또는 아이디
- (1-1) 상황별 가이드 메시지
### 2.2 비밀번호 인풋박스
- 입력 시 마지막 글자 제외 후 마스킹 처리
- (2-1) 상황별 가이드 메시지
### 2.3 열람 버튼
- 클릭: 열람/숨김 토글
- 디폴트: 숨김 상태
- 열람 상태일 시 (2) 영역 마스킹 해제 처리
### 2.4 자동 로그인 체크박스
- 클릭: 체크 설정/해제 토글
- 체크 시 로그아웃 전까지 세션 유지
### 2.5 로그인 버튼
- 클릭: 유효할 경우 대시보드 화면으로 이동
### 2.6 가이드 메시지
| 상황 | 가이드 메시지 |
|------|---------------|
| 필수 정보 미 입력 시 | 필수 정보입니다. |
| 4글자 미만 입력 시 | 이메일은 4자 이상 가능합니다 |
| 이메일 형식에 유효 하지 않을 경우 | 이메일 주소를 다시 확인해주세요. |
| 8자 미만 입력 시 | 8자 이상으로 만들어주세요. |
| 영문+숫자+특수문자 조합이 아닐 경우 | 영문, 숫자, 특수문자를 포함해주세요. 다음의 특수기호는 보안 사항을 가능합니다., ; - @ ! |
## 3. 사업자등록번호 조회 (슬라이드 17)
### 3.1 제조 데모
- 클릭: 제조 데모 화면으로 이동
### 3.2 시공 데모
- 클릭: 시공 데모 화면으로 이동
### 3.3 사업자등록번호 인풋박스
- 숫자만 가능, 10자리
### 3.4 다음 버튼
- 클릭:
1. 바로 빌 사업자등록번호 조회 후 사용 불가 경우: "중복된 사업자등록번호입니다." 알림 Alert 표시
2. 바로 빌 사업자등록번호 조회 후 사용 가능한 경우:
- [1] 테넌트 등록된 사업자등록번호일 경우: 테넌트 등록 전이어도 다른 영업사원이 등록중을 경우에는 사업자등록번호 사용 불가 (어드민에서는 해제 가능)
- "등록된 사업자등록번호 입니다." 알림 Alert 표시
- [2] 등록되지 않은 사업자등록번호일 경우: 회사정보 등록 화면으로 이동
## 4. 회사정보 등록 (슬라이드 18)
### 4.1 회사(테넌트) 상태
- 신청: 신청 완료 입력
- 승인: 자동 입금 외 계약금 50% 입금, 이메일로 URL 발송 선택
- 최초 로그인 시 ERP만 표시
- 거절: 프로젝트 설정 완료, 잔금 50% 입금 및 인도, 당월 말일까지는 무료, 익월부터 익월말까지 사용하고 구독료 청구
- 만료: 기간 종료, 안료외 연체 상태 구분?? 영업사원에게 알림, 서비스에는 접근 불가, 배너
- 해지: 오퍼 대기기간 단계 필요??
- 해지: 서비스 이용 불가
- 탈퇴: 로그인 불가, 복구 불가??
- 미사: 서비스 이용 불가
### 4.2 회사 로고 이미지 영역
- 디폴드 이미지 표시
- 클릭: 파일탐색기 팝업 표시, 10MB 이하의 PNG, JPEG, GIF 중 하나 선택 가능
### 4.3 우편번호 찾기 버튼
- 클릭: 선정한 주소 팝업 표시
### 4.4 찾기 버튼
- 클릭: 파일탐색기 팝업 표시, 이미지 또는 파일 하나 선택 가능
### 4.5 가입 신청 버튼
- 클릭: 사업자등록번호 조회 화면으로 이동
- 회사 로고만 선택, 나머지는 필수 정보
- 클릭: 가입 신청 완료 화면으로 이동
### 4.6 입력 필드
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 회사 로고 | N | 750x250px, 10MB 이하 PNG, JPEG, GIF |
| 회사명 | Y | |
| 대표자명 | Y | |
| 업태 | Y | |
| 업종 | Y | |
| 주소 | Y | 우편번호 찾기 + 상세주소 |
| 이메일 (아이디) | Y | |
| 세금계산서 이메일 | Y | |
| 담당자명 | Y | |
| 담당자 연락처 | Y | |
| 사업자등록증 | Y | 파일 첨부 |
## 5. 가입 신청 완료 (슬라이드 19)
### 5.1 가입 신청 완료 안내 문구 표시
- SAM은 폐쇄형 네트워크이므로 플랫폼의 무결성을 보장하기 위해 모든 계정을 검토해야 합니다.
- (회사명)의 계정 세부 정보를 추가로 확인하기 위해 추가 정보를 요청할 수 있습니다.
- 영업일 기준 3일 이내에 계정에 대한 업데이트를 받게 됩니다.
### 5.2 가입 신청 취소 버튼
- 클릭: "가입 신청 취소 시 등록한 모든 정보가 삭제됩니다. 정말 가입 신청을 취소하시겠습니까?" 확인 Alert 표시
## 6. 가입 신청 승인 성공 이메일 (슬라이드 20)
### 6.1 계정 활성화 버튼
- 클릭: 약관 동의 화면으로 이동
### 6.2 지원, 블로그 버튼
- 클릭: 해당 운영 노션 링크로 이동
## 7. 약관 동의 (슬라이드 21)
### 7.1 약관 영역
- 클릭: (1-1) 약관 내용 영역 열림/닫힘 토글
- 디폴트: 닫힘
### 7.2 체크박스
- 클릭: 체크 설정/해제 토글
- 디폴트: 체크 설정 해제
### 7.3 약관에 동의합니다 버튼
- 모든 필수 약관 동의 시 버튼 활성화
- 클릭: 비밀번호 설정 화면으로 이동
### 7.4 약관에 동의합니다 버튼
- 클릭: 모든 필수, 선택 약관에 동의 처리, 비밀번호 설정 화면으로 이동
### 7.5 약관 목록
| 약관명 | 필수 |
|--------|------|
| [필수] 서비스 이용약관 | Y |
| [필수] 개인정보 취급방침 | Y |
| [필수] 약관명 | Y |
| 마케팅 정보 수신 동의 (선택) | N |
| - 이메일 수신 동의 | N |
| - SMS 수신 동의 | N |
## 8. 비밀번호 설정 (슬라이드 22)
### 8.1 계정 활성화 버튼
- 클릭: 로그인 화면으로 이동
### 8.2 비밀번호 요구사항
- 최소 8자 이상 영문+숫자+특수문자 조합
---
## API 도출
### 인증 API
```
POST /api/auth/login # 로그인
POST /api/auth/logout # 로그아웃
POST /api/auth/password/reset # 비밀번호 재설정
POST /api/auth/password/change # 비밀번호 변경
```
### 회원가입/영업 API
```
GET /api/business-registration/verify # 사업자등록번호 조회
POST /api/registration/company # 회사정보 등록
POST /api/registration/cancel # 가입신청 취소
GET /api/terms # 약관 목록 조회
POST /api/terms/agree # 약관 동의
POST /api/account/activate # 계정 활성화
```
### 테넌트 API
```
GET /api/tenants # 테넌트 목록
POST /api/tenants # 테넌트 추가
PUT /api/tenants/{id} # 테넌트 수정
GET /api/tenants/{id}/switch # 테넌트 전환
```

View File

@@ -0,0 +1,144 @@
# GPS 출퇴근 (슬라이드 23-27)
## 1. 개요
GPS 기반 모바일 출퇴근 관리 시스템으로, 위치 정보를 활용하여 출근/퇴근을 기록합니다.
## 2. 마이페이지 팝업 > 출퇴근하기 (슬라이드 24)
### 2.1 출퇴근 버튼
- GPS 출퇴근 사용 시에만 표시
- 모바일일 경우에만 버튼 활성화
- 클릭: 출퇴근하기 화면으로 이동
### 2.2 출퇴근 허용 반경
- 기준 화표로부터의 출퇴근 허용 반경을 m 형으로 표시 (기준정보 > 출퇴근관리에서 설정)
### 2.3 현재 위치 버튼
- 클릭: (3-1) 해당 현재 위치를 지도 중심으로 표시
### 2.4 [+] 버튼
- 클릭: 지도 영역 확대
### 2.5 확대/축소 슬라이드바
- 드래그&드랍 또는 클릭: 지도 영역 확대/축소
### 2.6 [-] 버튼
- 클릭: 지도 영역 축소
### 2.7 개인 정보 영역
- 항목: 프로필 이미지, 이름, 부서명, 직급명
### 2.8 현재 시:분:초 표시
- HH:MM:SS
### 2.9 출근하기 버튼
- 클릭:
1) 출근 위치 미설정 상태일 경우: "출근 위치를 설정해주세요." 알림 Alert 표시
2) 출근 위치 설정 상태일 경우:
- (1) 출근 위치가 기준 설정 반경 초과일 경우: "출근 가능 위치가 아닙니다. 출근 위치를 확인해주세요." 알림 Alert 표시
- [2] GPS 출근 위치 기준 설정 반경 이내: 출근하기 화면으로 이동 (출근 기록 저장)
## 3. 출근하기 (슬라이드 25)
### 3.1 퇴근하기 버튼
- 클릭:
1) 퇴근 위치 미설정 상태일 경우: "퇴근 위치를 설정해주세요." 알림 Alert 표시
2) 퇴근 위치 설정 상태일 경우:
- (1) 퇴근 위치가 기준 설정 반경 초과일 경우: "퇴근 가능 위치가 아닙니다. 퇴근 위치를 확인해주세요." 알림 Alert 표시
- [2] GPS 퇴근 위치 기준 설정 반경 이내: 퇴근하기 화면으로 이동 (퇴근 기록 저장)
### 3.2 출근 완료 아이콘 이미지 표시
### 3.3 출근 완료 정보
- 항목: 출근 완료 문구, 시:분:초, 일자(요일)
### 3.4 출근 화표의 본사/현장명 표시
### 3.5 확인 버튼
- 클릭: 대시보드로 이동
## 4. 퇴근하기 (슬라이드 26)
### 4.1 퇴근 완료 아이콘 이미지 표시
### 4.2 퇴근 완료 정보
- 항목: 퇴근 완료 문구, 시:분:초, 일자(요일)
### 4.3 퇴근 화표의 본사/현장명 표시
### 4.4 확인 버튼
- 클릭: 대시보드로 이동
## 5. 현장 등록 (슬라이드 27)
### 5.1 위치 정보 설정
- 각 현장의 GPS 중심값으로 설정
### 5.2 입력 필드
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 주소 | Y | 우편번호 찾기 + 상세주소 |
| 경도 | Y | |
| 위도 | Y | |
| 첨부파일 | N | |
---
## 데이터 모델
### Attendance (출퇴근 기록)
```
- id: bigint
- user_id: bigint (FK)
- tenant_id: bigint (FK)
- type: enum('check_in', 'check_out')
- check_time: datetime
- latitude: decimal(10,8)
- longitude: decimal(11,8)
- location_id: bigint (FK, nullable) # 본사/현장
- location_name: string
- is_valid: boolean # 유효 위치 여부
- created_at: timestamp
```
### Location (본사/현장)
```
- id: bigint
- tenant_id: bigint (FK)
- name: string
- address: string
- detail_address: string
- latitude: decimal(10,8)
- longitude: decimal(11,8)
- allowed_radius: int # 허용 반경 (m)
- is_active: boolean
- created_at: timestamp
```
---
## API 도출
### GPS 출퇴근 API
```
POST /api/attendance/check-in # 출근 기록
POST /api/attendance/check-out # 퇴근 기록
GET /api/attendance/today # 오늘 출퇴근 현황
GET /api/attendance/status # 현재 출퇴근 상태
GET /api/attendance/location/validate # 위치 유효성 검증
```
### 현장 관리 API
```
GET /api/locations # 현장 목록
POST /api/locations # 현장 등록
PUT /api/locations/{id} # 현장 수정
DELETE /api/locations/{id} # 현장 삭제
```
### 출퇴근 설정 API
```
GET /api/settings/attendance # 출퇴근 설정 조회
PUT /api/settings/attendance # 출퇴근 설정 수정
```

View File

@@ -0,0 +1,381 @@
# 인사관리 (슬라이드 28-46)
## 1. 개요
인사관리 모듈은 부서, 사원, 근태, 휴가를 관리합니다.
## 2. 부서관리 (슬라이드 30-31)
### 2.1 전체 선택 체크박스
- 클릭: 전체 선택 설정/해제 토글
- 디폴트: 설정 해제 상태
### 2.2 개별 선택 체크박스
- 클릭: 개별 선택 설정/해제 토글
- 디폴트: 설정 해제 상태
### 2.3 추가 버튼
- 클릭: 선택된 부서의 하위 부서 일괄 생성
- 관리 권한이 없을 경우 숨김
### 2.4 삭제 버튼
- 클릭: "선택한 부서 N개를 삭제하시겠습니까?" 확인 Alert 표시
- 확인 선택 시 삭제된 부서 사원의 인원은 회사(기본) 인원으로 변경
### 2.5 확대 버튼
- 클릭: (6) 확대 버튼으로 변경, 하위 부서 숨김 처리
### 2.6 축소 버튼
- 클릭: (5) 축소 버튼으로 변경, 하위 부서 표시 처리
### 2.7 추가 버튼
- 클릭: 부서 추가 팝업 표시
### 2.8 수정 버튼
- 클릭: 부서 수정 팝업 표시
### 2.9 부서 추가/수정 팝업
- 부서명 인풋박스: 기존 부서명 표시, 수정 가능
## 3. 사원관리 (슬라이드 32-40)
### 3.1 기간 설정 영역
- 입사일 기준
### 3.2 기간 설정 버튼 영역
- 종류: 당해년도, 전전월, 전월, 당월, 어제, 오늘
- 클릭: 해당 기간이 (1) 영역에 설정되며 화면 전체에 적용 처리
### 3.3 CSV 일괄 등록 버튼
- 클릭: CSV 일괄 등록 화면으로 이동
### 3.4 사원 등록 버튼
- 클릭: 사원 상세 화면으로 이동
### 3.5 사용자 초대 버튼
- 클릭: 사용자 초대 팝업 표시
### 3.6 필터 셀렉트 박스
- 종류: 전체, 사용자 아이디 보유, 사용자 아이디 미보유, 재직, 휴직, 퇴직
- 디폴트: 전체
### 3.7 정렬 셀렉트 박스
- 종류: 직급순, 부서 오름차순, 부서 내림차순, 이름 오름차순, 이름 내림차순
- 디폴트: 직급순
### 3.8 현황 카드
| 항목 | 설명 |
|------|------|
| 현직 | 전체 현직 사원 수 |
| 휴직 | 전체 휴직 사원 수 |
| 퇴직 | (1) 해당 기간의 퇴직 사원 수 |
| 평균근속년수 | 전체 현직 사원 기준 |
## 4. 사원 상세 (슬라이드 33-36)
### 4.1 사원 정보 영역 (필수)
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 이름 | Y | |
| 주민등록번호 | Y | |
| 휴대폰 | Y | |
| 이메일 | Y | |
| 연봉 | N | |
| 급여계좌 은행 | N | 은행 선택 |
| 계좌 | N | |
| 예금주 | N | |
### 4.2 사용자 정보 영역
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 아이디 | N | 이메일 또는 아이디 |
| 비밀번호 | N | 최소 8자 이상 영문+숫자+특수문자 조합 |
| 권한 | N | 권한관리의 목록 표시 |
| 상태 | N | 종류: 정상, 재직, 중지. 재직 상태인 경우 로그아웃 처리, 로그인 시 '재정중인 아이디입니다.' 팝업 |
### 4.3 사원 상세 영역 (선택)
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 프로필 사진 | N | 1250x250px, 10MB 이하 PNG, JPEG, GIF |
| 사원코드 | N | |
| 성별 | N | 남성/여성 |
| 주소 | N | 우편번호 찾기 + 상세주소 |
### 4.4 인사 정보 영역
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 입사일 | N | |
| 고용 형태 | N | 종류: 정규직, 계약직, 파견직, 용역직, 시간제 근로자 |
| 직급 | N | 직급관리 화면에서 설정 |
| 상태 | N | 종류: 재직, 병가휴직, 육아휴직, 개인사정휴직, 무급휴직, 퇴사, 해고, 권고사직, 계약만료, 정년퇴직 |
| 부서 | N | 부서관리 화면에서 설정 |
| 직책 | N | 직책관리 화면에서 설정 |
| 출근 위치 | N | 본사, 현장 목록 |
| 퇴근 위치 | N | 본사, 현장 목록 |
| 퇴사일 | N | |
| 퇴사 사유 | N | |
### 4.5 항목 설정 버튼
- 클릭: 항목 설정 팝업 표시
### 4.6 항목 설정 팝업 (슬라이드 34)
- 전체 설정 ON/OFF 버튼
- 개별 설정 ON/OFF 버튼
- 디폴트: 설정 OFF 상태
## 5. CSV 일괄 등록 (슬라이드 39-40)
### 5.1 양식 다운로드 버튼
- 클릭: 등록된 양식 CSV 다운로드
### 5.2 파일 선택 버튼
- 클릭: 파일 탐색기 팝업, CSV 1개만 등록
### 5.3 파일 변환 버튼
- 클릭: CSV 데이터를 (3-1) 정보 등록 영역에 변환값 표시
- 범위: 사원 상세 화면의 전체 항목
### 5.4 등록 버튼
- 파일변환 완료 & (2) 체크 설정 항목 있을 경우에만 버튼 활성화
- 클릭: "(등록)의 정보를 정말 등록하시겠습니까?" 확인 Alert 표시
- 확인 클릭 시 (2) 체크 된 정보만 등록, "정보 등록이 완료되었습니다." 알림 Alert 표시
## 6. 사용자 초대 (슬라이드 37-38)
### 6.1 사용자 초대 프로세스
- 초대 이메일로 발송 → 약관 동의 (아이디를 이메일로 사용) → 비밀번호 설정 → 로그인
### 6.2 초대할 이메일 주소 인풋박스
- ','로 구분하여 여러 주소 입력 가능
### 6.3 권한 셀렉트 박스, 검색
- 종류: 권한관리의 목록 표시
### 6.4 초대 메시지 인풋박스
### 6.5 초대 버튼
- 클릭: 사용자 초대 이메일 발송
- 이메일 주소 기준 사원 정보가 있을 경우에는 대량하여 사용자 등록 처리
- 없을 경우에는 사용자에만 등록하고 나머지 사원 정보는 직접 입력 필요
- 사용자 아이디 중복 불가
## 7. 근태관리 (슬라이드 41-42)
### 7.1 근태관리 현황 카드
| 항목 | 설명 |
|------|------|
| 정시 출근 | 전체 정시 출근 수 |
| 지각 | 전체 지각 수 |
| 결근 | 전체 결근 수 |
| 휴가 | 전체 휴가 수 |
### 7.2 기간 설정 버튼 영역
- 종류: 당해년도, 전전월, 전월, 당월, 어제, 오늘
- 클릭: 해당 기간이 설정되며 화면 전체에 적용 처리
- 근태관리 자동 설정 시: 모든 사원이 정시 출퇴근한 것으로 기록, 예외사항만 경우 작성
### 7.3 근태 등록 버튼
- 클릭: 근태 정보 팝업 표시
### 7.4 사유 등록 버튼
- 클릭: 사유 정보 팝업 표시
### 7.5 필터 셀렉트 박스
- 종류: 전체, 정시 출근, 지각, 결근, 휴가, 출장, 외근, 연장근무
- 디폴트: 전체
### 7.6 정렬 셀렉트 박스
- 종류: 직급순, 부서 오름차순, 부서 내림차순, 이름 오름차순, 이름 내림차순
- 디폴트: 직급순
### 7.7 근태 정보 팝업
| 필드명 | 설명 |
|--------|------|
| 대상 | 사원 셀렉트 박스, 검색&다중 선택 |
| 기준일 | 달력 팝업 표시, 일자 다중 선택 가능, 디폴트: 당일 |
| 출근 시간 | 시/분 선택 |
| 퇴근 시간 | 시/분 선택 |
| 야간 연장 시간 | 주당 연장 근로 시간에서 주말 연장 시간을 차감한 이내에만 설정 가능 |
| 주말 연장 시간 | 주당 연장 근로 시간에서 야간 연장 시간을 차감한 이내에만 설정 가능 |
### 7.8 사유 정보 팝업
| 필드명 | 설명 |
|--------|------|
| 대상 | 사원 셀렉트 박스 |
| 기준일 | 달력 팝업 |
| 내용 | 내용 입력 |
## 8. 휴가관리 (슬라이드 43-46)
### 8.1 휴가관리 탭
- 휴가 사용 현황
- 휴가 부여 현황
- 휴가 신청 현황
### 8.2 휴가관리 현황 카드
| 항목 | 설명 |
|------|------|
| 휴가 승인 대기 | |
| 연차 | 전체 연차 사원 수 |
| 결조사 | |
| 연간 연차 사용률 | |
### 8.3 필터 셀렉트 박스
- 종류: 전체, 모든 사원 목록
- 항목: 부서명, 직급명, 사원명 표시
- 디폴트: 전체
### 8.4 정렬 셀렉트 박스
- 종류: 직급순, 부서 오름차순, 부서 내림차순, 이름 오름차순, 이름 내림차순
- 디폴트: 직급순
### 8.5 휴가 부여 팝업
| 필드명 | 설명 |
|--------|------|
| 유형 | 종류: 연차, 보상, 경조, 보건, 병가, 반차, 반수 선택 |
| 사유 | |
| 일수 | 개월/일/시간 선택 |
### 8.6 휴가 신청 팝업
| 필드명 | 설명 |
|--------|------|
| 잔여 | 잔여 일수 표시 |
| 유형 | 종류: 연차, 보상, 경조, 보건, 병가, 반차 |
| 기간 | 날짜 선택, (5) 반차 선택 시에는 시간으로 변경 |
### 8.7 휴가 신청 현황 필터
- 종류: 승인, 거절
- 1/3 완료: 결재선 승인 진행도에 따라 표시
---
## 데이터 모델
### Department (부서)
```
- id: bigint
- tenant_id: bigint (FK)
- parent_id: bigint (FK, nullable)
- name: string
- order: int
- is_active: boolean
- created_at: timestamp
```
### Employee (사원)
```
- id: bigint
- tenant_id: bigint (FK)
- user_id: bigint (FK, nullable)
- department_id: bigint (FK, nullable)
- employee_code: string
- name: string
- resident_number: string (encrypted)
- phone: string
- email: string
- salary: decimal
- bank_code: string
- account_number: string
- account_holder: string
- profile_image: string
- gender: enum('male', 'female')
- address: string
- hire_date: date
- employment_type: enum('regular', 'contract', 'dispatch', 'service', 'part_time')
- position_id: bigint (FK)
- job_title_id: bigint (FK)
- status: enum('active', 'leave', 'sick_leave', 'parental_leave', 'personal_leave', 'unpaid_leave', 'resigned', 'dismissed', 'recommended_resignation', 'contract_expired', 'retired')
- check_in_location_id: bigint (FK, nullable)
- check_out_location_id: bigint (FK, nullable)
- resignation_date: date
- resignation_reason: text
- created_at: timestamp
```
### AttendanceRecord (근태 기록)
```
- id: bigint
- tenant_id: bigint (FK)
- employee_id: bigint (FK)
- date: date
- check_in_time: time
- check_out_time: time
- status: enum('on_time', 'late', 'absent', 'leave', 'business_trip', 'outside_work', 'overtime')
- night_overtime_hours: decimal
- weekend_overtime_hours: decimal
- reason: text
- created_at: timestamp
```
### Leave (휴가)
```
- id: bigint
- tenant_id: bigint (FK)
- employee_id: bigint (FK)
- type: enum('annual', 'reward', 'congratulation', 'health', 'sick', 'half_day', 'half_day_am', 'half_day_pm')
- start_date: date
- end_date: date
- days: decimal
- reason: text
- status: enum('pending', 'approved', 'rejected')
- created_at: timestamp
```
### LeaveBalance (휴가 잔여)
```
- id: bigint
- tenant_id: bigint (FK)
- employee_id: bigint (FK)
- year: int
- type: enum('annual', 'reward', ...)
- granted: decimal
- used: decimal
- remaining: decimal
```
---
## API 도출
### 부서관리 API
```
GET /api/departments # 부서 목록 (트리 구조)
POST /api/departments # 부서 추가
PUT /api/departments/{id} # 부서 수정
DELETE /api/departments/{id} # 부서 삭제
DELETE /api/departments/bulk # 부서 일괄 삭제
```
### 사원관리 API
```
GET /api/employees # 사원 목록
POST /api/employees # 사원 등록
GET /api/employees/{id} # 사원 상세
PUT /api/employees/{id} # 사원 수정
DELETE /api/employees/{id} # 사원 삭제
POST /api/employees/import # CSV 일괄 등록
GET /api/employees/template # CSV 템플릿 다운로드
POST /api/employees/invite # 사용자 초대
```
### 근태관리 API
```
GET /api/attendance # 근태 목록
POST /api/attendance # 근태 등록
PUT /api/attendance/{id} # 근태 수정
GET /api/attendance/summary # 근태 현황 요약
POST /api/attendance/reason # 사유 등록
```
### 휴가관리 API
```
GET /api/leaves # 휴가 목록
POST /api/leaves # 휴가 신청
PUT /api/leaves/{id} # 휴가 수정
DELETE /api/leaves/{id} # 휴가 취소
POST /api/leaves/{id}/approve # 휴가 승인
POST /api/leaves/{id}/reject # 휴가 거절
GET /api/leaves/balance/{employee_id} # 휴가 잔여 조회
POST /api/leaves/grant # 휴가 부여
GET /api/leaves/summary # 휴가 현황 요약
```

View File

@@ -0,0 +1,296 @@
# 전자결재 (슬라이드 47-59)
## 1. 개요
전자결재 모듈은 기안함, 결재함, 참조함으로 구성되며, 품의서, 지출결의서, 지출 예상 내역서 등의 문서를 처리합니다.
## 2. 기안함 (슬라이드 48-54)
### 2.1 문서 상태
| 상태 | 설명 |
|------|------|
| 임시저장 | 문서 작성 중, 임시저장된 상태 |
| 진행 | 모든 결재자 중 일부 승인된 상태, 결재 요청을 받은 상태 |
| 결재요청 | 결재 요청을 받은 상태 |
| 반려 | 결재자 중 반려한 사람이 있는 상태 |
### 2.2 기안함 현황 카드
| 항목 | 설명 |
|------|------|
| 진행 | 진행 중인 문서 수 |
| 전체 | 전체 문서 수 |
| 반려 | 반려된 문서 수 |
| 임시 저장 | 임시저장 문서 수 |
### 2.3 문서 작성 버튼
- 클릭: 문서 작성 화면으로 이동
### 2.4 상세 버튼
- 클릭: 문서 상세 팝업 표시
### 2.5 삭제 버튼
- 클릭:
1) 임시저장 상태일 경우: "정말 이(1)건을 삭제 처리하시겠습니까?" 확인 Alert 표시, 확인 시 해당 문서 삭제 처리
2) 임시저장 상태가 아닐 경우: "임시저장 상태만 삭제가 가능합니다." 알림 Alert 표시
### 2.6 수정 버튼
- 클릭:
1) 임시저장 상태일 경우: 문서 작성 화면으로 이동
2) 임시저장 상태가 아닐 경우: 문서 상세 팝업 표시
### 2.7 필터 셀렉트 박스
- 종류: 전체, 임시저장, 진행, 완료, 반려
- 디폴트: 전체
### 2.8 정렬 셀렉트 박스
- 종류: 최신순, 등록순
- 디폴트: 최신순
## 3. 문서 작성 (슬라이드 49-54)
### 3.1 상세 버튼
- 클릭: 문서 상세 팝업 표시
### 3.2 문서 유형 셀렉트 박스, 검색
- 종류: 품의서, 지출결의서, 지출 예상 내역서
- 선택한 문서 유형의 화면으로 변경 표시
### 3.3 결재자 셀렉트 박스, 검색&다중 선택
- 항목: 부서명, 직책명, 사원명 표시
- 종류: 전체, 모든 사원 목록
- 디폴트: 전체
### 3.4 참조자 셀렉트 박스, 검색&다중 선택
- 항목: 부서명, 직책명, 사원명 표시
- 종류: 전체, 모든 사원 목록
- 디폴트: 전체
### 3.5 버튼 영역
| 버튼 | 설명 |
|------|------|
| 상세 | 문서 상세 팝업 표시 |
| 삭제 | 임시저장 상태만 삭제 가능 |
| 삼신 | 결재선 마감 경우 숨김 |
| 임시저장 | 임시저장 처리 |
## 4. 품의서 (슬라이드 50)
### 4.1 구매처 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 구매처 | Y | |
| 구매처 결제일 | Y | |
### 4.2 품의서 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 제목 | Y | |
| 품의 내역 | Y | 녹음 버튼: 마이크 사용 가능할 경우에만 버튼 활성화, 녹음 중지 버튼으로 변경, 음성 내용을 텍스트로 변경하여 (1-1) 인풋박스 영역에 표시 |
| 품의 사유 | Y | |
| 예상 비용 | Y | |
### 4.3 참고 이미지 정보
- 첨부파일 찾기
## 5. 지출결의서 (슬라이드 51-52)
### 5.1 지출 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 지출 요청일 | Y | |
| 결제일 | Y | |
### 5.2 지출결의서 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 적요 | Y | |
| 금액 | Y | |
| 비고 | N | |
### 5.3 결제 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 카드 | Y | 종류: 등록된 카드 목록, 디폴트: 첫번째 카드 |
| 총 비용 | - | 지출결의서 정보의 금액 합계 표시 |
### 5.4 참고 이미지 정보
- 첨부파일 찾기
## 6. 지출 예상 내역서 (슬라이드 53-54)
### 6.1 지출 예상 내역서 목록
- 체크 설정/해제 토글
- 1) 지출 예상 내역서 화면에서 찾을 경우: 설정된 체크 상태 유지
- 2) 문서 작성 화면에서 설정했을 경우: 모든 체크 항목 설정된 상태
### 6.2 항목
| 필드명 | 설명 |
|--------|------|
| 예상 지급일 | |
| 품목 | |
| 지출금액 | |
| 거래처 | |
| 계좌 | |
### 6.3 합계
| 항목 | 설명 |
|------|------|
| 지출 합계 | |
| 계좌 잔액 | |
| 최종 차액 | |
## 7. 결재함 (슬라이드 55-58)
### 7.1 상태
| 상태 | 설명 |
|------|------|
| 진행 | 결재 하위 상태 |
| 예정 | 결재 순번에 의한 대기 |
| 결재요청 | 결재 요청을 받은 상태 |
### 7.2 결재함 현황 카드
| 항목 | 설명 |
|------|------|
| 결재 요청 | 결재 요청 문서 수 |
| 완료 | 완료 문서 수 |
| 반려 | 반려 문서 수 |
| 예정 | 예정 문서 수 |
### 7.3 승인 버튼
- 클릭: "정말 (1)건을 승인하시겠습니까?" 확인 Alert 표시
- 확인 버튼 클릭 시 "승인이 완료되었습니다." 알림 Alert 표시
### 7.4 반려 버튼
- 클릭: "정말 (1)건을 반려하시겠습니까?" 확인 Alert 표시
- 확인 버튼 클릭 시 "반려가 완료되었습니다." 알림 Alert 표시
### 7.5 필터 셀렉트 박스
- 종류: 전체, 결재 요청, 예정, 완료, 반려
- 1/3 완료: 결재선 승인 진행도에 따라 표시
- 디폴트: 전체
### 7.6 수정 버튼
- 클릭: 문서 상세 팝업 표시
## 8. 참조함 (슬라이드 59)
### 8.1 열람 버튼
- 클릭: "정말 (1)건을 열람 처리하시겠습니까?" 확인 Alert 표시
- 확인 버튼 클릭 시 "열람 처리가 완료되었습니다." 알림 Alert 표시
### 8.2 미열람 버튼
- 클릭: "정말 (1)건을 미열람 처리하시겠습니까?" 확인 Alert 표시
- 확인 버튼 클릭 시 "미열람 처리가 완료되었습니다." 알림 Alert 표시
### 8.3 필터 셀렉트 박스
- 종류: 전체, 열람, 미열람
- 디폴트: 전체
## 9. 문서 상세 팝업 (슬라이드 56-58)
### 9.1 버튼 영역
| 버튼 | 설명 |
|------|------|
| 복제 | 문서 작성 화면으로 이동 (새글) |
| 수정 | 결재선 중에서는 누구나 수정 가능, 해당 문서 작성 화면으로 이동 |
| 반려 | 결재선 마감 경우 숨김 |
| 승인 | 결재선 마감 경우 숨김 |
| 인쇄 | |
| 공유 | (5-1) 팝업 표시 |
| 닫기 | |
### 9.2 결재선 영역
- 승인/반려 시 해당 아이콘 표시
### 9.3 공유 버튼
| 공유 방식 | 설명 |
|-----------|------|
| PDF | |
| 이메일 | |
| 카카오톡 | |
---
## 데이터 모델
### ApprovalDocument (결재 문서)
```
- id: bigint
- tenant_id: bigint (FK)
- document_number: string
- document_type: enum('request', 'expense', 'expense_estimate')
- title: string
- status: enum('draft', 'pending', 'in_progress', 'approved', 'rejected')
- drafter_id: bigint (FK) # 기안자
- content: json # 문서 내용
- attachments: json
- created_at: timestamp
- submitted_at: timestamp
```
### ApprovalLine (결재선)
```
- id: bigint
- document_id: bigint (FK)
- approver_id: bigint (FK)
- order: int
- status: enum('pending', 'approved', 'rejected')
- comment: text
- approved_at: timestamp
```
### ApprovalReference (참조자)
```
- id: bigint
- document_id: bigint (FK)
- referee_id: bigint (FK)
- is_read: boolean
- read_at: timestamp
```
### ExpenseItem (지출 항목)
```
- id: bigint
- document_id: bigint (FK)
- description: string
- amount: decimal
- note: text
```
---
## API 도출
### 기안함 API
```
GET /api/approvals/drafts # 기안 목록
POST /api/approvals # 문서 작성
PUT /api/approvals/{id} # 문서 수정
DELETE /api/approvals/{id} # 문서 삭제
POST /api/approvals/{id}/submit # 문서 제출 (결재 요청)
POST /api/approvals/{id}/save-draft # 임시저장
GET /api/approvals/{id} # 문서 상세
GET /api/approvals/drafts/summary # 기안함 현황
```
### 결재함 API
```
GET /api/approvals/inbox # 결재함 목록
POST /api/approvals/{id}/approve # 승인
POST /api/approvals/{id}/reject # 반려
GET /api/approvals/inbox/summary # 결재함 현황
```
### 참조함 API
```
GET /api/approvals/references # 참조함 목록
POST /api/approvals/{id}/mark-read # 열람 처리
POST /api/approvals/{id}/mark-unread # 미열람 처리
```
### 문서 공유 API
```
GET /api/approvals/{id}/pdf # PDF 다운로드
POST /api/approvals/{id}/share/email # 이메일 공유
POST /api/approvals/{id}/share/kakao # 카카오톡 공유
```

View File

@@ -0,0 +1,427 @@
# 회계관리 (슬라이드 60-91)
## 1. 개요
회계관리 모듈은 거래처, 매출, 매입, 입금, 출금, 어음, 거래처원장, 미수금 현황, 입출금 계좌 조회 등을 관리합니다.
## 2. 회계관리 플로우차트 (슬라이드 61)
### 2.1 매출 플로우
```
거래처 선택 → 매출 등록 → 세금계산서 발행
```
### 2.2 입금 플로우
```
입금 등록 → 전액 입금? → 어음 수취?
↓ ↓
입출금 계좌 조회 어음관리
```
### 2.3 매입 플로우
```
거래처 선택 → 매입 등록 → 세금계산서 수취
```
### 2.4 출금 플로우
```
출금 등록 → 전액 출금? → 어음 발행?
↓ ↓
입출금 계좌 조회 어음관리
```
### 2.5 추심 플로우
```
미수금 현황 → 연체? → 악성주심? → 악성 추심
미지급 알림
```
### 2.6 조회
- 입출금 계좌 조회
- 카드 내역 조회
### 2.7 장부/보고서
- 거래처원장
- 지출 예상 내역서
- 일일 일보
## 3. 거래처관리 (슬라이드 62-65)
### 3.1 현황 카드
| 항목 | 설명 |
|------|------|
| 전체 거래처 | |
| 매출 거래처 | |
| 매입 거래처 | |
### 3.2 삭제 버튼
- 관리 권한이 없을 경우 숨김
- 클릭: "선택한 거래처 N개를 삭제하시겠습니까?" 확인 Alert 표시
- 확인 선택 시 삭제
### 3.3 구분 필터 셀렉트 박스
- 종류: 전체, 매출, 매입, 매입매출
- 디폴트: 전체
### 3.4 신용등급 필터 셀렉트 박스
- 종류: 전체, AAA, AA, A, BBB, BB, B, CCC, CC, C, D
- 디폴트: 전체
### 3.5 거래등급 필터 셀렉트 박스
- 종류: 전체, A(우수), B(양호), C(보통), D(주의), E(위험)
- 디폴트: 전체
### 3.6 약정체결 필터 셀렉트 박스
- 종류: 전체, 약정체결, 정상
- 디폴트: 전체
### 3.7 정렬 셀렉트 박스
- 종류: 최신순, 등록순, 거래처명 오름차순, 거래처명 내림차순, 미수금 높은순, 미수금 낮은순
- 디폴트: 최신순
## 4. 거래처 상세 (슬라이드 63-65)
### 4.1 기본 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 사업자등록번호 | Y | |
| 거래처 코드 | N | |
| 거래처명 | Y | |
| 대표자명 | Y | |
| 거래처 유형 | Y | 매출매입 선택 |
| 업태 | N | |
| 업종 | N | |
### 4.2 연락처 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 주소 | N | 우편번호 찾기 + 상세주소 |
| 전화번호 | N | |
| 모바일 | N | |
| 팩스 | N | |
| 이메일 | N | |
### 4.3 담당자 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 담당자명 | N | |
| 담당자 전화 | N | |
### 4.4 시스템 관리자
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 관리자명 | N | |
### 4.5 회사 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 회사 로고 | N | 750x250px, 10MB 이하 PNG, JPEG, GIF |
### 4.6 결제 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 매입 결제일 | N | 종류: 1일~31일, 말일. 디폴트: 10일. 거래처 유형이 '매입' 또는 '매입매출'일 경우 해당 |
| 매출 결제일 | N | 종류: 1일~31일, 말일. 디폴트: 15일. 거래처 유형이 '매출' 또는 '매입매출'일 경우 표시 |
### 4.7 신용 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 신용등급 | N | 외부 신용평가 등급 표시. 예: AAA, AA, A, BBB, BB, B, CCC, CC, C, D |
| 거래등급 | N | 종류: A(우수), B(양호), C(보통), D(주의), E(위험). 디폴트: A(우수). 자사 기준 거래처 평가 등급 |
### 4.8 계좌 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 세금계산서 이메일 | N | |
| 입금계좌 은행 | N | 은행 선택 |
| 계좌 | N | |
| 예금주 | N | |
### 4.9 추가 정보
| 필드명 | 설명 |
|--------|------|
| 미수금 | 해당 거래처의 현재 미수금 잠게 표시. 읽기 전용 |
| 연체 | - ON: 연체 상태로 표시, 연체일수 표시 |
| | - OFF: 정상 상태 |
| | - 거래처 상세에서 연체 설정과 연동 |
| | - (4-1) 연체 등록 이후부터 경과일 표시 |
| 미지급 | 해당 거래처에 대한 미지급금 잠게 표시. 읽기 전용 |
| 악성채권 | - ON: 악성채권으로 등록, 악성채권 추심관리 목록에 표시 |
| | - OFF: 정상 상태 |
| | - 디폴트: OFF |
| 메모 | 추가 버튼 클릭 시 목록 최상단에 추가 |
## 5. 매출관리 (슬라이드 66-71)
### 5.1 매출 유형
- 필드 매출 시 매출 직접 등록 (삭제 가능)
- 필드 매출: 용역 매출, 공사 매출, 임대 수익, 기타 매출
### 5.2 매출 상세
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 매출번호 | - | 자동 채번 |
| 매출일 | Y | |
| 거래처명 | Y | 거래처 선택 |
| 매출 유형 | Y | 선택 |
### 5.3 품목 정보
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 품목명 | Y | |
| 수량 | Y | |
| 단가 | Y | |
| 공급가액 | - | 자동 계산 |
| 부가세 | - | 자동 계산 |
| 적요 | N | |
### 5.4 세금계산서
| 필드명 | 설명 |
|--------|------|
| 세금계산서 발행 | 토글 |
### 5.5 거래명세서
| 필드명 | 설명 |
|--------|------|
| 거래명세서 | 토글 |
## 6. 입금관리 (슬라이드 74-76)
### 6.1 입금 유형
- 종류: 매출대금, 선수금, 가수금, 입대수익, 미자수익, 보증금 반환, 차입금, 자본금, 부가세 환급, 기타, 미상정
### 6.2 입금 상세
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 입금일 | Y | |
| 입금계좌 | Y | 국민은행 1234 (계좌명) |
| 입금자명 | Y | |
| 입금금액 | Y | |
| 적요 | N | |
| 거래처 | N | 선택 |
| 입금 유형 | Y | |
## 7. 출금관리 (슬라이드 77-79)
### 7.1 출금 유형
- 종류: 매입대금, 선급금, 가지급금, 임대비용, 보증금 지급, 차입금 상환, 배당금 지급, 세금, 공과금, 경비, 4대보험, 급여
### 7.2 출금 상세
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 출금일 | Y | |
| 출금계좌 | Y | |
| 받는 분 | Y | |
| 출금금액 | Y | |
| 적요 | N | |
| 거래처 | N | |
| 출금 유형 | Y | |
## 8. 거래처원장 (슬라이드 80)
### 8.1 현황 카드
| 항목 | 설명 |
|------|------|
| 전기 이월 | |
| 매출 | |
| 수금 | |
| 잔액 | |
### 8.2 거래처원장 목록
- 거래처별 기간별 합계 금액 표시
- 클릭: 거래처원장 상세 화면으로 이동
### 8.3 엑셀 다운로드 버튼
- 클릭: 엑셀 파일 다운로드
## 9. 미수금 현황 (슬라이드 85)
### 9.1 미수금 현황 목록
- 거래처별 월별 미수금 현황 (매출, 입금, 어음, 미수금, 메모)
### 9.2 수취 어음 등록 시 표시
- 회계에는 미반영
### 9.3 연체 표시
- ON: 연체 상태로 표시, 연체일수 표시
- OFF: 정상 상태
- 거래처 상세에서 연체 설정과 연동
### 9.4 엑셀 다운로드 버튼
### 9.5 저장 버튼
## 10. 입출금 계좌 조회 (슬라이드 90)
### 10.1 설명
- 기준 정보 > 계좌 관리에 등록된 계좌의 자동 입출금 내역 수집
### 10.2 새로고침 버튼
- 클릭: 은행 계좌 입출금 내역 최신 데이터 조회
- 바로빌 API 연동 시 실시간 조회
### 10.3 구분 필터 셀렉트 박스
- 종류: 전체, 출금, 입금
- 디폴트: 전체
### 10.4 계정과목 필터 셀렉트 박스, 검색&다중 선택
- (2) 선택에 따른 계정과목 목록 표시
- 입금 종류: 전체, 매출대금, 선수금, 가수금, 임대수익, 이자수익, 보증금 반환, 차입금, 자본금, 부가세 환급, 기타, 미상정
- 출금 종류: 전체, 매입대금, 선급금, 가지급금, 임대비용, 이자비용, 보증금 지급, 차입금 상환, 배당금 지급, 세금, 공과금, 경비, 4대보험, 급여
- 디폴트: 전체
### 10.5 정렬 셀렉트 박스
- 종류: 최신순, 등록순, 금액순
- 디폴트: 최신순
### 10.6 수정 버튼
- 클릭: 해당 입금/출금 상세 화면으로 이동
---
## 데이터 모델
### Vendor (거래처)
```
- id: bigint
- tenant_id: bigint (FK)
- business_number: string
- vendor_code: string
- name: string
- representative: string
- type: enum('sales', 'purchase', 'both')
- business_type: string
- business_category: string
- address: string
- phone: string
- mobile: string
- fax: string
- email: string
- manager_name: string
- manager_phone: string
- logo: string
- purchase_payment_day: int
- sales_payment_day: int
- credit_rating: enum('AAA', 'AA', 'A', 'BBB', 'BB', 'B', 'CCC', 'CC', 'C', 'D')
- trade_rating: enum('A', 'B', 'C', 'D', 'E')
- tax_invoice_email: string
- bank_code: string
- account_number: string
- account_holder: string
- is_overdue: boolean
- overdue_start_date: date
- is_bad_debt: boolean
- notes: json
- created_at: timestamp
```
### Sales (매출)
```
- id: bigint
- tenant_id: bigint (FK)
- vendor_id: bigint (FK)
- sales_number: string
- sales_date: date
- sales_type: enum('product', 'service', 'construction', 'rental', 'other')
- total_amount: decimal
- tax_amount: decimal
- is_tax_invoice_issued: boolean
- is_statement_issued: boolean
- created_at: timestamp
```
### SalesItem (매출 품목)
```
- id: bigint
- sales_id: bigint (FK)
- item_name: string
- quantity: int
- unit_price: decimal
- supply_amount: decimal
- tax_amount: decimal
- description: string
```
### Deposit (입금)
```
- id: bigint
- tenant_id: bigint (FK)
- vendor_id: bigint (FK, nullable)
- account_id: bigint (FK)
- deposit_date: date
- depositor_name: string
- amount: decimal
- description: string
- deposit_type: enum('sales', 'advance', 'suspense', 'rental', 'interest', 'deposit_return', 'loan', 'capital', 'vat_refund', 'other', 'unidentified')
- created_at: timestamp
```
### Withdrawal (출금)
```
- id: bigint
- tenant_id: bigint (FK)
- vendor_id: bigint (FK, nullable)
- account_id: bigint (FK)
- withdrawal_date: date
- recipient_name: string
- amount: decimal
- description: string
- withdrawal_type: enum('purchase', 'advance', 'suspense', 'rental', 'interest', 'deposit', 'loan_repayment', 'dividend', 'tax', 'utility', 'expense', 'insurance', 'salary')
- created_at: timestamp
```
---
## API 도출
### 거래처 API
```
GET /api/vendors # 거래처 목록
POST /api/vendors # 거래처 등록
GET /api/vendors/{id} # 거래처 상세
PUT /api/vendors/{id} # 거래처 수정
DELETE /api/vendors/{id} # 거래처 삭제
DELETE /api/vendors/bulk # 거래처 일괄 삭제
GET /api/vendors/summary # 거래처 현황
```
### 매출 API
```
GET /api/sales # 매출 목록
POST /api/sales # 매출 등록
GET /api/sales/{id} # 매출 상세
PUT /api/sales/{id} # 매출 수정
DELETE /api/sales/{id} # 매출 삭제
POST /api/sales/{id}/tax-invoice # 세금계산서 발행
GET /api/sales/summary # 매출 현황
```
### 입금 API
```
GET /api/deposits # 입금 목록
POST /api/deposits # 입금 등록
GET /api/deposits/{id} # 입금 상세
PUT /api/deposits/{id} # 입금 수정
DELETE /api/deposits/{id} # 입금 삭제
```
### 출금 API
```
GET /api/withdrawals # 출금 목록
POST /api/withdrawals # 출금 등록
GET /api/withdrawals/{id} # 출금 상세
PUT /api/withdrawals/{id} # 출금 수정
DELETE /api/withdrawals/{id} # 출금 삭제
```
### 장부/보고서 API
```
GET /api/ledger/vendor # 거래처원장
GET /api/ledger/vendor/{vendor_id} # 거래처원장 상세
GET /api/ledger/vendor/export # 거래처원장 엑셀 다운로드
GET /api/receivables # 미수금 현황
GET /api/receivables/export # 미수금 현황 엑셀 다운로드
GET /api/bank-transactions # 입출금 계좌 조회
POST /api/bank-transactions/sync # 입출금 내역 동기화
```

View File

@@ -0,0 +1,332 @@
# 기준정보 (슬라이드 92-104)
## 1. 개요
기준정보 모듈은 직급, 직책, 권한, 근무, 출퇴근, 휴가, 카드, 계좌, 팝업, 게시판, 일반설정, 알림설정을 관리합니다.
## 2. 직급관리 (슬라이드 93, 95)
### 2.1 직급 인풋박스
- 직급 입력 후 추가 버튼 클릭
### 2.2 추가 버튼
- 클릭: (2-1) 직급 목록 최하단에 표시
### 2.3 직급 목록
- 디폴트: 사원, 대리, 과장, 차장, 부장, 이사, 상무, 전무, 부사장, 사장
### 2.4 순서 변경 버튼
- 드래그&드랍: 해당 위치로 순서 변경
### 2.5 수정 버튼
- 클릭: 직급 수정 팝업 표시
### 2.6 삭제 버튼
- 클릭:
1) 해당 직급으로 사원 설정된 경우: "'직급명'을 사용하고 있는 사원이 있습니다. 다 변경 후 삭제가 가능합니다." 알림 Alert 표시
2) 해당 직급으로 사원 미설정된 경우: "정말 삭제하시겠습니까?" 확인 Alert 표시, 확인 클릭 시 "삭제가 완료되었습니다." 알림 Alert 표시
### 2.7 직급 수정 팝업
- 직급명 인풋박스: 기존 직급명 표시, 수정 가능
## 3. 직책관리 (슬라이드 94-95)
### 3.1 기능
- 직급관리와 동일한 구조
### 3.2 디폴트 직책
- (없음)
### 3.3 직책 수정 팝업
- 직책명 인풋박스: 기존 직책명 표시, 수정 가능
## 4. 권한관리 (슬라이드 96)
### 4.1 관리자
- 디폴트: 모든 메뉴 권한 설정
- 수정 불가, 삭제 불가
### 4.2 일반
- 디폴트: 대시보드 제외 모든 메뉴 접근 불가
### 4.3 추가 버튼
- 클릭: 권한 추가 팝업 표시
### 4.4 삭제 버튼
- 클릭:
1) 해당 권한으로 사원 설정된 경우: "'권한명'을 사용하고 있는 사원이 있습니다. 다 변경 후 삭제가 가능합니다." 알림 Alert 표시
2) 해당 권한으로 사원 미설정된 경우: "정말 삭제하시겠습니까?" 확인 Alert 표시
### 4.5 체크박스
- 클릭: 체크 설정/해제 토글
- 디폴트: 체크 해제 상태
### 4.6 관리
- 읽기 및 수정 권한
### 4.7 읽기
- 읽기만 권한
## 5. 근무관리 (슬라이드 97)
### 5.1 근무 정보
| 필드명 | 설명 |
|--------|------|
| 근무 유형 | 종류: 고정형, 변형, 맞춤형 |
| 기본 소정 근로 시간 | 주 00시간 |
| 연장 근로 시간 | 주 00시간 |
| 연장 근로 한도 | 52시간 미만 |
| 근무 요일 설정 | 클릭: 설정된 체크 활성/비활성 토글, 디폴트: 월~금 활성 |
### 5.2 출근 시간
- 09:00
### 5.3 퇴근 시간
- 18:00
### 5.4 휴게 시간
| 필드명 | 설명 |
|--------|------|
| 총 휴게 시간 | 1시간 |
| 휴게 시간 | 12:00 ~ 13:00 |
## 6. 출퇴근관리 (슬라이드 99)
### 6.1 출퇴근 설정
| 필드명 | 설명 |
|--------|------|
| GPS 출퇴근 | 토글: 기준 좌표로 정해진 거리 기준 이내에 있을 때만 출근/퇴근 등록 가능 |
| 허용 반경 | 주 00m |
### 6.2 본사 위치 정보
| 필드명 | 설명 |
|--------|------|
| 본사 주소 | 우편번호 찾기 + 상세주소 |
| 경도 | |
| 위도 | |
### 6.3 현장 목록
- 추가 버튼: 현장 등록 팝업 표시
- 현장명, 주소, 삭제 버튼
## 7. 휴가관리 (슬라이드 100)
### 7.1 기준 셀렉트 박스
- 종류: 회계연도, 입사일
- 디폴트: 회계연도
- 입사일 선택 시 (2) 영역 비활성화
- 회계연도 기준: 회사의 회계연도를 기준으로 휴가를 부여하고 조회할 수 있습니다.
- 입사일 기준: 사원의 입사일 회계연도 기준으로 휴가를 부여하고 조회할 수 있습니다.
### 7.2 기준일 월/일 설정 영역
- 회계연도 기준 시에만 활성화
### 7.3 기본 연차 설정
- 1년간 출근율 80% 이상이면 15일
- 3년 이상 근속 시 2년에 1일 추가 (최대 25일)
- 1년 미만 또는 출근율 80% 이하인 경우 1일
- 입사일~회계연도 기준일 사정: 회년도 출근율로 판정 80% 이상이면 15일
- (3-1) 연차+1년+1일 시작, 이후 2년에 1일 추가
- 입사일~회계연도 기준으로 전환할 때는 취업규칙 변경, 노사 의견수렴, 전환 시 충북 연차 정산(입사일 기준 vs 회계연도 기준 비교 후 부족분 보전)을 반드시 검토 필요
## 8. 카드관리 (슬라이드 101-102)
### 8.1 카드 목록
| 필드명 | 설명 |
|--------|------|
| 카드사 | |
| 카드번호 | 앞4자리, 끝4자리 표시 |
| 카드명 | |
| 상태 | |
| 사용자 | 부서명 / 이름 / 직책 |
| 작업 | 상세 버튼 |
### 8.2 필터
- 종류: 전체, 사용, 정지
- 디폴트: 전체
### 8.3 카드 상세
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 카드사 | Y | 카드사명 선택 |
| 카드번호 | Y | 1234-1234-1234-1234 |
| 유효기간 | Y | MM/YY |
| 카드 비밀번호 앞 2자리 | Y | 입력 시 마스킹 처리 |
| 카드명 | Y | |
| 상태 | Y | 종류: 사용, 정지. 정지 시 해당 카드의 자동 조회 중단 |
| 사용자 | N | 부서명 / 이름 / 직책. 선택 시 해당 카드의 사용자로 설정 |
## 9. 계좌관리 (슬라이드 103-104)
### 9.1 계좌 목록
| 필드명 | 설명 |
|--------|------|
| 은행 | |
| 계좌번호 | |
| 예금주 | |
| 상태 | |
| 사용자 | 부서명 / 이름 / 직책 |
| 작업 | 상세 버튼 |
### 9.2 필터
- 종류: 전체, 사용, 정지
- 디폴트: 전체
### 9.3 계좌 상세
| 필드명 | 필수 | 설명 |
|--------|------|------|
| 은행 | Y | 은행 선택 |
| 계좌번호 | Y | |
| 예금주 | Y | |
| 계좌명 | Y | |
| 상태 | Y | 종류: 사용, 정지. 정지 시 해당 계좌의 자동 조회 중단 |
| 사용자 | N | 부서명 / 이름 / 직책. 선택 시 해당 계좌의 사용자로 설정 |
---
## 데이터 모델
### Position (직급)
```
- id: bigint
- tenant_id: bigint (FK)
- name: string
- order: int
- created_at: timestamp
```
### JobTitle (직책)
```
- id: bigint
- tenant_id: bigint (FK)
- name: string
- order: int
- created_at: timestamp
```
### Role (권한)
```
- id: bigint
- tenant_id: bigint (FK)
- name: string
- is_system: boolean # 시스템 기본 권한 여부
- permissions: json # 메뉴별 권한 설정
- created_at: timestamp
```
### WorkSetting (근무 설정)
```
- id: bigint
- tenant_id: bigint (FK)
- work_type: enum('fixed', 'flexible', 'custom')
- standard_hours: int # 주당 소정 근로 시간
- overtime_hours: int # 주당 연장 근로 시간
- overtime_limit: int # 연장 근로 한도
- work_days: json # ['mon', 'tue', 'wed', 'thu', 'fri']
- start_time: time
- end_time: time
- break_hours: int
- break_start: time
- break_end: time
```
### AttendanceSetting (출퇴근 설정)
```
- id: bigint
- tenant_id: bigint (FK)
- use_gps: boolean
- allowed_radius: int # 허용 반경 (m)
- headquarters_address: string
- headquarters_latitude: decimal(10,8)
- headquarters_longitude: decimal(11,8)
```
### LeaveSetting (휴가 설정)
```
- id: bigint
- tenant_id: bigint (FK)
- base_type: enum('fiscal_year', 'hire_date')
- fiscal_start_month: int
- fiscal_start_day: int
```
### Card (카드)
```
- id: bigint
- tenant_id: bigint (FK)
- card_company: string
- card_number: string (encrypted)
- expiry_date: string
- card_password: string (encrypted)
- card_name: string
- status: enum('active', 'inactive')
- user_id: bigint (FK, nullable)
- created_at: timestamp
```
### BankAccount (계좌)
```
- id: bigint
- tenant_id: bigint (FK)
- bank_code: string
- account_number: string
- account_holder: string
- account_name: string
- status: enum('active', 'inactive')
- user_id: bigint (FK, nullable)
- created_at: timestamp
```
---
## API 도출
### 직급/직책 API
```
GET /api/positions # 직급 목록
POST /api/positions # 직급 추가
PUT /api/positions/{id} # 직급 수정
DELETE /api/positions/{id} # 직급 삭제
PUT /api/positions/reorder # 직급 순서 변경
GET /api/job-titles # 직책 목록
POST /api/job-titles # 직책 추가
PUT /api/job-titles/{id} # 직책 수정
DELETE /api/job-titles/{id} # 직책 삭제
PUT /api/job-titles/reorder # 직책 순서 변경
```
### 권한 API
```
GET /api/roles # 권한 목록
POST /api/roles # 권한 추가
GET /api/roles/{id} # 권한 상세
PUT /api/roles/{id} # 권한 수정
DELETE /api/roles/{id} # 권한 삭제
GET /api/menus # 메뉴 목록 (권한 설정용)
```
### 설정 API
```
GET /api/settings/work # 근무 설정 조회
PUT /api/settings/work # 근무 설정 수정
GET /api/settings/attendance # 출퇴근 설정 조회
PUT /api/settings/attendance # 출퇴근 설정 수정
GET /api/settings/leave # 휴가 설정 조회
PUT /api/settings/leave # 휴가 설정 수정
```
### 카드/계좌 API
```
GET /api/cards # 카드 목록
POST /api/cards # 카드 등록
GET /api/cards/{id} # 카드 상세
PUT /api/cards/{id} # 카드 수정
DELETE /api/cards/{id} # 카드 삭제
GET /api/bank-accounts # 계좌 목록
POST /api/bank-accounts # 계좌 등록
GET /api/bank-accounts/{id} # 계좌 상세
PUT /api/bank-accounts/{id} # 계좌 수정
DELETE /api/bank-accounts/{id} # 계좌 삭제
```

View File

@@ -0,0 +1,225 @@
# 보고서 및 분석 (슬라이드 105-113)
## 1. 개요
보고서 및 분석 모듈은 일일 일보, 지출 예상 내역서, 가지급금 인정이자 계산, AI 리포트 생성 등을 제공합니다.
## 2. 일일 일보 (슬라이드 106-107)
### 2.1 일일 일보 조회
- 매일 전일의 입출금 및 매출 매입 현황 자동 집계
### 2.2 현황 카드
| 항목 | 설명 |
|------|------|
| 전일 잔액 | 조회 기준일 전일 잔액 |
| 당일 입금액 | 전일 입금 합계 |
| 당일 출금액 | 전일 출금 합계 |
| 당일 잔액 | 조회 기준일 잔액 |
### 2.3 일일 일보 목록
| 필드명 | 설명 |
|--------|------|
| 구분 | 입금/출금 |
| 거래처명 | |
| 계정과목 | |
| 입금액 | |
| 출금액 | |
| 적요 | |
### 2.4 엑셀 다운로드 버튼
## 3. 지출 예상 내역서 (슬라이드 108-109)
### 3.1 지출 예상 내역서 조회
- 예상 지출 금액 및 일정 조회
### 3.2 현황 카드
| 항목 | 설명 |
|------|------|
| 예상 지출 합계 | 월별 예상 지출 합계 |
| 계좌 잔액 | 현재 계좌 잔액 |
| 예상 잔액 | 계좌 잔액 - 예상 지출 합계 |
### 3.3 지출 예상 내역서 목록
| 필드명 | 설명 |
|--------|------|
| 예상 지급일 | |
| 품목 | |
| 지출금액 | |
| 거래처 | |
| 계좌 | |
### 3.4 엑셀 다운로드 버튼
### 3.5 월별 합계
| 항목 | 설명 |
|------|------|
| 2025/11 계 | 11월 지출 합계 |
| 2025/12 계 | 12월 지출 합계 |
| 지출 합계 | 전체 지출 합계 |
| 계좌 잔액 | |
| 최종 차액 | |
## 4. 가지급금 인정이자 계산 (슬라이드 110-112)
### 4.1 가지급금 인정이자 계산 예시 (2024년 기준)
- 인정이자율 4.6% (당좌대출이자율 기준, 매년 고시)
### 4.2 계산 예시
| 항목 | 금액 |
|------|------|
| 가지급금 잔액 | 15,200,000원 |
| 인정이자 | 699,200원 |
| 법인세 추가 (19%) | 132,848원 |
| 대표자 소득세 추가 (35%) | 244,720원 |
| 대표자 지방소득세 (10%) | 24,472원 |
| **총 세금 부담** | **402,040원** |
### 4.3 계산식
```
잔액 × 0.046 = 인정이자
인정이자 × 0.19 = 법인세 추가
인정이자 × 0.35 = 대표자 소득세 추가
```
### 4.4 기본 정산 공식
```
정산차액 = 가지급금 총액 - 실사용 총액
```
### 4.5 인정이자 계산 공식 (법인세법 기준)
```
경과일수 = 정산일 - 지급일
일이자율 = 연이자율 ÷ 365
인정이자 = 가지급금 × 일이자율 × 경과일수
```
## 5. AI 리포트 생성 (슬라이드 113)
### 5.1 AI 리포트 생성 프롬프트
#### 작성 규칙
1. 문장은 간결하고 명확하게 작성
2. 숫자는 읽기 쉽게 "3,123,000원", "15%" 형식 사용
3. 계정과목명, 거래처명은 구체적으로 명시
4. 조치가 필요한 경우 구체적인 행동 권한 포함
5. 긍정적 변화도 반드시 실상 포함
6. 법인세, 소득세 영향이 있는 경우 세무 리스크 명시
#### 키워드 강조 규칙
출력 메시지 내 다음 키워드는 프론트엔드에서 색상 강조됩니다:
- **빨간색(경고)**: 초과, 증가, 발생, 필요, 불가
- **주황색(주의)**: 점검, 확인, 주의, 검토
- **녹색(긍정)**: 감소, 완료, 정상
- **파란색(양호)**: 여유, 적정, 양호
#### 예시 출력
입력 데이터 예시에 대한 출력:
```json
{"리포트": [
{"영역": "지출분석", "상태": "경고", "메시지": "이번 달 예상 지출이 전월 대비 15% 증가했습니다.", "상세": "매입 비용 증가가 주요 원인입니다."},
{"영역": "가지급금", "상태": "주의", "메시지": "50일 이상 잔기 미수금 3건(2,500만원) 발생.", "상세": "회수 조치가 필요합니다."},
{"영역": "카드/계좌", "상태": "경고", "메시지": "법인카드 사용 한도 85% 도달, 잔여 한도 600만원입니다.", "상세": "사용 계획을 점검해 주세요."},
{"영역": "미수금", "상태": "주의", "메시지": "미수금에 대한 관리가 필요한 상태입니다.", "상세": ""}
],
"요약": "지출 증가와 정기 미수금에 대한 관리가 필요한 상태입니다."}
```
---
## 데이터 모델
### DailyReport (일일 일보)
```
- id: bigint
- tenant_id: bigint (FK)
- report_date: date
- previous_balance: decimal
- daily_deposit: decimal
- daily_withdrawal: decimal
- current_balance: decimal
- details: json # 입출금 상세 내역
- created_at: timestamp
```
### ExpenseEstimate (지출 예상 내역서)
```
- id: bigint
- tenant_id: bigint (FK)
- expected_date: date
- item_name: string
- amount: decimal
- vendor_id: bigint (FK, nullable)
- account_id: bigint (FK, nullable)
- created_at: timestamp
```
### LoanInterestCalculation (가지급금 인정이자 계산)
```
- id: bigint
- tenant_id: bigint (FK)
- calculation_date: date
- loan_balance: decimal
- interest_rate: decimal
- recognized_interest: decimal
- corporate_tax_addition: decimal
- income_tax_addition: decimal
- local_tax_addition: decimal
- total_tax_burden: decimal
- created_at: timestamp
```
### AIReport (AI 리포트)
```
- id: bigint
- tenant_id: bigint (FK)
- report_date: date
- report_type: string
- content: json # 리포트 내용
- summary: text
- created_at: timestamp
```
---
## API 도출
### 일일 일보 API
```
GET /api/reports/daily # 일일 일보 조회
GET /api/reports/daily/export # 일일 일보 엑셀 다운로드
```
### 지출 예상 내역서 API
```
GET /api/reports/expense-estimate # 지출 예상 내역서 조회
POST /api/reports/expense-estimate # 지출 예상 내역 등록
PUT /api/reports/expense-estimate/{id}# 지출 예상 내역 수정
DELETE /api/reports/expense-estimate/{id}# 지출 예상 내역 삭제
GET /api/reports/expense-estimate/export # 지출 예상 내역서 엑셀 다운로드
```
### 가지급금 인정이자 API
```
GET /api/reports/loan-interest # 가지급금 인정이자 계산 조회
POST /api/reports/loan-interest/calculate # 가지급금 인정이자 계산 실행
```
### AI 리포트 API
```
GET /api/reports/ai # AI 리포트 목록
POST /api/reports/ai/generate # AI 리포트 생성
GET /api/reports/ai/{id} # AI 리포트 상세
DELETE /api/reports/ai/{id} # AI 리포트 삭제
```
### 대시보드/분석 API
```
GET /api/dashboard/summary # 대시보드 요약
GET /api/dashboard/charts # 대시보드 차트 데이터
GET /api/analytics/sales # 매출 분석
GET /api/analytics/expense # 지출 분석
GET /api/analytics/receivables # 미수금 분석
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,661 @@
# ItemMaster 연동 설계서
**작성일**: 2025-12-05
**최종 수정**: 2025-12-08
**버전**: 1.1
**상태**: Draft
---
## 1. 개요
### 1.1 목적
품목기준관리(ItemMaster)에서 정의한 필드와 실제 엔티티 데이터를 연동하여, 동적 필드 정의 및 값 저장을 가능하게 한다.
### 1.2 설계 원칙
- **기존 테이블 활용**: 신규 테이블 추가 없이 기존 `attributes` JSON 컬럼 활용
- **범용성**: 품목(products, materials) 외에도 다른 엔티티(orders, clients 등) 확장 가능
- **성능**: JOIN 없이 단일 쿼리로 조회 가능
- **유연성**: 테넌트/그룹별 다른 필드 구성 지원
---
## 2. 현재 구조
### 2.1 ItemMaster 테이블 구조
```
┌─────────────────────────────────────────────────────────────┐
│ item_pages (페이지 정의) │
├─────────────────────────────────────────────────────────────┤
│ id, tenant_id, page_name, item_type, source_table, │
│ is_active │
│ │
│ item_type: FG(완제품), PT(반제품), SM(부자재), │
│ RM(원자재), CS(소모품) │
│ │
│ source_table: 실제 저장 테이블명 │
│ - 'products' (FG, PT) │
│ - 'materials' (SM, RM, CS) │
└─────────────────────────────────────────────────────────────┘
│ 1:N
┌─────────────────────────────────────────────────────────────┐
│ item_sections (섹션 정의) │
├─────────────────────────────────────────────────────────────┤
│ id, tenant_id, page_id, title, type, order_no │
│ │
│ type: fields(필드형), bom(BOM형) │
└─────────────────────────────────────────────────────────────┘
│ 1:N
┌─────────────────────────────────────────────────────────────┐
│ item_fields (필드 정의) │
├─────────────────────────────────────────────────────────────┤
│ id, tenant_id, group_id, section_id (nullable) │
│ field_key ← attributes JSON 키와 매핑 │
│ field_name ← 화면 표시명 │
│ field_type ← textbox, number, dropdown, checkbox... │
│ is_required ← 필수 여부 │
│ default_value ← 기본값 │
│ placeholder ← 입력 힌트 │
│ validation_rules ← 검증 규칙 JSON │
│ options ← 선택 옵션 JSON │
│ properties ← 추가 속성 JSON │
│ category ← 필드 카테고리 │
│ is_common ← 공통 필드 여부 │
│ is_active ← 활성 여부 │
│ │
│ [내부용 매핑 컬럼 - API 응답에서 hidden] │
│ source_table ← 원본 테이블명 (products, materials 등) │
│ source_column ← 원본 컬럼명 (code, name 등) │
│ storage_type ← 저장방식 (column=DB컬럼, json=JSON) │
│ json_path ← JSON 저장 경로 (예: attributes.size) │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 엔티티 테이블 구조
```
┌─────────────────────────────────────────────────────────────┐
│ products │
├─────────────────────────────────────────────────────────────┤
│ [고정 필드] │
│ id, tenant_id, code, name, unit, category_id │
│ product_type, is_active, is_sellable, is_purchasable... │
│ │
│ [동적 필드] │
│ attributes JSON ← ItemMaster 필드 값 저장 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ materials │
├─────────────────────────────────────────────────────────────┤
│ [고정 필드] │
│ id, tenant_id, material_code, name, unit, category_id │
│ material_type, is_active... │
│ │
│ [동적 필드] │
│ attributes JSON ← ItemMaster 필드 값 저장 │
│ options JSON ← 추가 옵션 저장 │
└─────────────────────────────────────────────────────────────┘
```
---
## 3. 연동 설계
### 3.1 매핑 규칙
```
ItemMaster Entity.attributes
┌──────────────────────┐ ┌──────────────────────┐
│ group_id: 1 │ │ │
│ field_key: "color" │ ◀═══매핑═══▶ │ {"color": "빨강"} │
│ field_key: "weight" │ ◀═══매핑═══▶ │ {"weight": 1.5} │
│ field_key: "spec" │ ◀═══매핑═══▶ │ {"spec": "10x20"} │
└──────────────────────┘ └──────────────────────┘
핵심: item_fields.field_key = attributes JSON의 key
```
### 3.2 Group ID 정의
| group_id | 엔티티 | 대상 테이블 | 비고 |
|----------|--------|-------------|------|
| 1 | 품목-제품 | products | product_type: FG, PT |
| 2 | 품목-자재 | materials | material_type: SM, RM, CS |
| 3 | 주문 | orders | 향후 확장 |
| 4 | 고객 | clients | 향후 확장 |
| ... | ... | ... | 필요 시 추가 |
> **참고**: group_id는 `common_codes` 테이블에서 관리하거나, 별도 enum으로 정의 가능
### 3.3 데이터 흐름
```
┌─────────────────────────────────────────────────────────────┐
│ 1. 관리자: ItemMaster에서 필드 정의 │
├─────────────────────────────────────────────────────────────┤
│ │
│ POST /api/v1/item-master/fields │
│ { │
│ "group_id": 1, │
│ "field_key": "color", │
│ "field_name": "색상", │
│ "field_type": "dropdown", │
│ "is_required": true, │
│ "options": [ │
│ {"label": "빨강", "value": "red"}, │
│ {"label": "파랑", "value": "blue"} │
│ ] │
│ } │
│ │
│ → item_fields 테이블에 저장 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 2. 사용자: 품목 등록 화면 진입 │
├─────────────────────────────────────────────────────────────┤
│ │
│ GET /api/v1/item-master/fields?group_id=1 │
│ │
│ → 정의된 필드 목록 반환 │
│ → 프론트엔드가 동적 폼 렌더링 │
│ │
│ ┌────────────────────────────────────┐ │
│ │ [색상 ▼] ← dropdown으로 표시 │ │
│ │ 빨강 │ │
│ │ 파랑 │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 3. 사용자: 품목 저장 │
├─────────────────────────────────────────────────────────────┤
│ │
│ POST /api/v1/products │
│ { │
│ "code": "P-001", ← 고정 필드 │
│ "name": "티셔츠", │
│ "unit": "EA", │
│ "product_type": "FG", │
│ "attributes": { ← 동적 필드 │
│ "color": "red", (field_key: value) │
│ "size": "XL" │
│ } │
│ } │
│ │
│ → products 테이블에 저장 │
│ → attributes JSON에 동적 필드 값 포함 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 4. 사용자: 품목 조회 │
├─────────────────────────────────────────────────────────────┤
│ │
│ GET /api/v1/products/1 │
│ │
│ { │
│ "id": 1, │
│ "code": "P-001", │
│ "name": "티셔츠", │
│ "attributes": { │
│ "color": "red", │
│ "size": "XL" │
│ } │
│ } │
│ │
│ → JOIN 없이 한 번에 조회! │
└─────────────────────────────────────────────────────────────┘
```
---
## 4. API 설계
### 4.1 ItemMaster API (기존)
| Method | Endpoint | 설명 |
|--------|----------|------|
| GET | `/api/v1/item-master/fields` | 필드 목록 조회 |
| GET | `/api/v1/item-master/fields/{id}` | 필드 상세 조회 |
| POST | `/api/v1/item-master/fields` | 필드 생성 |
| PUT | `/api/v1/item-master/fields/{id}` | 필드 수정 |
| DELETE | `/api/v1/item-master/fields/{id}` | 필드 삭제 |
**필터 파라미터**:
- `group_id`: 엔티티 그룹 필터
- `section_id`: 섹션 필터
- `is_active`: 활성 필터
- `is_common`: 공통 필드 필터
### 4.2 엔티티 API 수정
#### 4.2.1 Products API
**저장 시 attributes 포함**:
```json
POST /api/v1/products
{
"code": "P-001",
"name": "제품명",
"unit": "EA",
"product_type": "FG",
"attributes": {
"color": "red",
"weight": 1.5,
"custom_field": "value"
}
}
```
**조회 시 필드 메타데이터 포함 (선택)**:
```
GET /api/v1/products/1?include_field_meta=true
```
```json
{
"id": 1,
"code": "P-001",
"name": "제품명",
"attributes": {
"color": "red",
"weight": 1.5
},
"field_meta": [
{
"field_key": "color",
"field_name": "색상",
"field_type": "dropdown",
"value": "red",
"options": [...]
},
{
"field_key": "weight",
"field_name": "중량",
"field_type": "number",
"value": 1.5
}
]
}
```
---
## 5. 검증 로직
### 5.1 저장 시 검증 흐름
```php
class ItemFieldValidationService
{
/**
* attributes 값을 ItemMaster 기준으로 검증
*/
public function validate(int $groupId, array $attributes): array
{
$errors = [];
// 1. 해당 그룹의 필드 정의 조회
$fields = ItemField::where('group_id', $groupId)
->where('is_active', true)
->get()
->keyBy('field_key');
// 2. 필수 필드 체크
foreach ($fields->where('is_required', true) as $field) {
if (!isset($attributes[$field->field_key])) {
$errors[$field->field_key] = "{$field->field_name}은(는) 필수입니다.";
}
}
// 3. 타입별 검증
foreach ($attributes as $key => $value) {
if (!$fields->has($key)) {
continue; // 정의되지 않은 필드는 스킵 (또는 에러)
}
$field = $fields->get($key);
$fieldError = $this->validateFieldValue($field, $value);
if ($fieldError) {
$errors[$key] = $fieldError;
}
}
return $errors;
}
/**
* 필드 타입별 값 검증
*/
private function validateFieldValue(ItemField $field, mixed $value): ?string
{
return match($field->field_type) {
'number' => $this->validateNumber($field, $value),
'dropdown' => $this->validateDropdown($field, $value),
'date' => $this->validateDate($field, $value),
'checkbox' => $this->validateCheckbox($field, $value),
default => null
};
}
private function validateNumber(ItemField $field, mixed $value): ?string
{
if (!is_numeric($value)) {
return "{$field->field_name}은(는) 숫자여야 합니다.";
}
$rules = $field->validation_rules ?? [];
if (isset($rules['min']) && $value < $rules['min']) {
return "{$field->field_name}은(는) {$rules['min']} 이상이어야 합니다.";
}
if (isset($rules['max']) && $value > $rules['max']) {
return "{$field->field_name}은(는) {$rules['max']} 이하여야 합니다.";
}
return null;
}
private function validateDropdown(ItemField $field, mixed $value): ?string
{
$options = $field->options ?? [];
$validValues = array_column($options, 'value');
if (!in_array($value, $validValues)) {
return "{$field->field_name}의 값이 유효하지 않습니다.";
}
return null;
}
}
```
### 5.2 Controller에서 사용
```php
class ProductsController extends Controller
{
public function store(ProductStoreRequest $request)
{
$validated = $request->validated();
// attributes 검증 (선택적)
if (isset($validated['attributes'])) {
$groupId = 1; // 품목-제품 그룹
$errors = $this->fieldValidationService->validate(
$groupId,
$validated['attributes']
);
if (!empty($errors)) {
return ApiResponse::error('검증 실패', $errors, 422);
}
}
$product = $this->productService->create($validated);
return ApiResponse::success($product, __('message.created'));
}
}
```
---
## 6. 프론트엔드 연동
### 6.1 동적 폼 렌더링 흐름
```
1. 페이지 로드 시
GET /api/v1/item-master/fields?group_id=1
2. 필드 정의 기반 폼 컴포넌트 렌더링
field_type: textbox → <Input />
field_type: number → <InputNumber />
field_type: dropdown → <Select options={field.options} />
field_type: checkbox → <Checkbox />
field_type: date → <DatePicker />
field_type: textarea → <Textarea />
3. 저장 시 attributes 객체 구성
{
[field_key]: value,
[field_key]: value,
...
}
```
### 6.2 React 컴포넌트 예시
```tsx
interface ItemField {
id: number;
field_key: string;
field_name: string;
field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
is_required: boolean;
default_value?: string;
placeholder?: string;
options?: Array<{ label: string; value: string }>;
validation_rules?: Record<string, any>;
}
function DynamicFieldRenderer({ field, value, onChange }: Props) {
switch (field.field_type) {
case 'textbox':
return (
<Input
value={value}
onChange={(e) => onChange(field.field_key, e.target.value)}
placeholder={field.placeholder}
required={field.is_required}
/>
);
case 'number':
return (
<InputNumber
value={value}
onChange={(val) => onChange(field.field_key, val)}
min={field.validation_rules?.min}
max={field.validation_rules?.max}
required={field.is_required}
/>
);
case 'dropdown':
return (
<Select
value={value}
onChange={(val) => onChange(field.field_key, val)}
options={field.options}
required={field.is_required}
/>
);
// ... 기타 타입
}
}
function ProductForm() {
const [fields, setFields] = useState<ItemField[]>([]);
const [attributes, setAttributes] = useState<Record<string, any>>({});
useEffect(() => {
// 필드 정의 로드
fetch('/api/v1/item-master/fields?group_id=1')
.then(res => res.json())
.then(data => setFields(data.data));
}, []);
const handleFieldChange = (key: string, value: any) => {
setAttributes(prev => ({ ...prev, [key]: value }));
};
return (
<form>
{/* 고정 필드 */}
<Input name="code" label="품목코드" required />
<Input name="name" label="품목명" required />
{/* 동적 필드 */}
{fields.map(field => (
<DynamicFieldRenderer
key={field.id}
field={field}
value={attributes[field.field_key]}
onChange={handleFieldChange}
/>
))}
</form>
);
}
```
---
## 7. 확장 가이드
### 7.1 새 엔티티 추가 시
1. **group_id 정의**: 새 그룹 ID 할당
2. **테이블 확인**: `attributes` JSON 컬럼 존재 확인 (없으면 추가)
3. **ItemMaster 필드 정의**: 해당 group_id로 필드 생성
4. **API 수정**: 저장/조회 시 attributes 처리 로직 추가
### 7.2 예시: 주문(orders) 연동
```sql
-- 1. orders 테이블에 attributes 컬럼 추가 (없는 경우)
ALTER TABLE orders ADD COLUMN attributes JSON DEFAULT NULL COMMENT '동적 필드';
-- 2. ItemMaster에 주문용 필드 정의
INSERT INTO item_fields (tenant_id, group_id, field_key, field_name, field_type, ...)
VALUES (1, 3, 'urgency', '긴급도', 'dropdown', ...);
```
---
## 8. 모델 헬퍼 메서드
### 8.1 ItemPage 모델
```php
class ItemPage extends Model
{
/**
* source_table에 해당하는 모델 클래스명 반환
*/
public function getTargetModelClass(): ?string
{
$mapping = [
'products' => \App\Models\Product::class,
'materials' => \App\Models\Material::class,
];
return $mapping[$this->source_table] ?? null;
}
/**
* 제품 페이지인지 확인
*/
public function isProductPage(): bool
{
return $this->source_table === 'products';
}
/**
* 자재 페이지인지 확인
*/
public function isMaterialPage(): bool
{
return $this->source_table === 'materials';
}
}
```
### 8.2 ItemField 모델
```php
class ItemField extends Model
{
/**
* 시스템 필드 여부 확인 (DB 컬럼과 매핑된 필드)
*/
public function isSystemField(): bool
{
return !is_null($this->source_table) && !is_null($this->source_column);
}
/**
* 컬럼 저장 방식 여부 확인
*/
public function isColumnStorage(): bool
{
return $this->storage_type === 'column';
}
/**
* JSON 저장 방식 여부 확인
*/
public function isJsonStorage(): bool
{
return $this->storage_type === 'json';
}
}
```
### 8.3 필드 저장 방식 판단
```
┌─────────────────────────────────────────────────────────────┐
│ storage_type 판단 로직 │
├─────────────────────────────────────────────────────────────┤
│ │
│ if (source_table && source_column) { │
│ // 시스템 필드 (기존 DB 컬럼과 매핑) │
│ if (storage_type === 'column') { │
│ → products.{source_column} 또는 │
│ materials.{source_column} 에서 직접 읽기/쓰기 │
│ } else if (storage_type === 'json') { │
│ → {json_path} 경로로 JSON 내 읽기/쓰기 │
│ } │
│ } else { │
│ // 커스텀 필드 (동적 정의) │
│ → attributes.{field_key} 에 저장 │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## 9. 구현 계획
| 순서 | 작업 | 담당 | 예상 공수 |
|------|------|------|----------|
| 1 | group_id 코드 정의 | BE | 0.5일 |
| 2 | ItemFieldValidationService 구현 | BE | 1일 |
| 3 | ProductsController 수정 (검증 연동) | BE | 0.5일 |
| 4 | MaterialsController 수정 (검증 연동) | BE | 0.5일 |
| 5 | API 응답에 field_meta 포함 옵션 | BE | 0.5일 |
| 6 | DynamicFieldRenderer 컴포넌트 | FE | 2일 |
| 7 | 품목 등록/수정 폼 연동 | FE | 1일 |
| 8 | 테스트 및 QA | 공통 | 1일 |
**총 예상 공수: 7일**
---
## 10. 변경 이력
| 날짜 | 버전 | 변경 내용 | 작성자 |
|------|------|----------|--------|
| 2025-12-05 | 1.0 | 최초 작성 | - |
| 2025-12-08 | 1.1 | source_table/source_column 매핑 컬럼 추가, 모델 헬퍼 메서드 문서화 | - |

137
system/mng-structure.md Normal file
View File

@@ -0,0 +1,137 @@
# MNG 관리자 패널 구조 현황
> **최종 갱신**: 2026-02-27
> **기술 스택**: Laravel 12 + HTMX 2 + DaisyUI 5 + Tailwind 3
---
## 1. 프로젝트 규모
| 항목 | 수량 |
|------|------|
| 컨트롤러 | 171 |
| 블레이드 템플릿 | 436 |
| 모델 | 185 |
| 서비스 | 98 |
| FormRequest | 37 |
| 미들웨어 | 4 |
| Console Commands | 4 |
| Traits | 4 |
---
## 2. 디렉토리 구조
```
app/
├── Http/Controllers/
│ ├── Api/Admin/ 관리자 API (바로빌, HR, PM, 견적)
│ ├── Auth/ 인증
│ ├── Barobill/ 바로빌 연동
│ ├── Credit/ 신용조회
│ ├── DevTools/ API Explorer, Flow Tester
│ ├── ESign/ 전자서명
│ ├── Finance/ 재무
│ ├── HR/ 인사
│ ├── Lab/ 실험/테스트
│ ├── Sales/ 영업
│ ├── Stats/ 통계
│ ├── System/ 시스템 설정
│ └── Video/ 영상/튜토리얼
├── Models/ api/ 모델과 별도 (동일 DB 사용)
│ ├── Admin/ PM 관리 (프로젝트, 태스크, 이슈)
│ ├── Barobill/ 바로빌
│ ├── Commons/ 공통 (파일, 메뉴)
│ ├── Documents/ 문서
│ ├── Equipment/ 설비
│ ├── ESign/ 전자서명
│ ├── Finance/ 재무
│ ├── HR/ 인사
│ ├── Items/ 품목
│ ├── Quote/ 견적
│ ├── Sales/ 영업
│ ├── Stats/ 통계
│ └── Tenants/ 테넌트
├── Services/ (98개)
│ ├── ApiExplorer/ API 탐색기
│ ├── Barobill/ 바로빌 연동
│ ├── FlowTester/ API 플로우 테스터
│ ├── HR/ 인사
│ ├── ProjectManagement/ PM
│ ├── Quote/ 견적
│ ├── Sales/ 영업
│ └── Video/ 영상
└── Traits/
├── BelongsToTenant
├── HasTenantFilter
├── ModelTrait
└── UppercaseAttributes
```
---
## 3. 뷰(블레이드) 도메인 구조
| 도메인 | 주요 뷰 디렉토리 | 기능 |
|--------|-----------------|------|
| barobill | bank-account, billing, card-usage, etax, hometax, kakaotalk, sms | 바로빌 연동 전체 |
| finance | accounts, fund-schedules, settlement, sales-commission | 재무/회계 |
| hr | attendances, employees, leaves, payrolls, employee-tenure | 인사관리 |
| sales | admin-prospects, business-cards, dashboard, interviews, products, prospects | 영업관리 |
| project-management | projects, tasks, issues | 프로젝트 관리 |
| dev-tools | api-explorer, flow-tester | 개발 도구 |
| system | ai-config, ai-token-usage, alerts, holidays | 시스템 설정 |
| equipment | inspections, repairs | 설비관리 |
| esign | sign/ | 전자서명 |
| video | tutorial, veo3 | 영상 관리 |
| quote-formulas | categories | 견적 공식 |
---
## 4. 아키텍처 패턴
### 프론트엔드
- **HTMX 2**: SPA 프레임워크 없이 서버 렌더링 + 부분 업데이트
- **DaisyUI 5** + Tailwind 3: UI 컴포넌트 + 스타일
- **Axios**: AJAX 호출 (HTMX 보조)
- **Vite 7**: 빌드 도구
### 백엔드
- **Service-First**: 비즈니스 로직 → Service 클래스
- **BelongsToTenant**: Multi-tenant 데이터 격리
- **FormRequest**: 입력 검증 (37개)
- **api와 동일 DB**: `samdb` 공유, 자체 모델 보유
### api ↔ mng 관계
- 동일 MySQL DB (`samdb`) 공유
- 각자 독립적인 모델 보유 (api: 205개, mng: 185개)
- **DB 마이그레이션은 api에서만** — mng에 migrations 없음
- 내부 통신: HMAC 기반 `INTERNAL_EXCHANGE_SECRET`
- 파일 공유: mng가 api의 `storage/app/tenants` 마운트
### 주요 기능 (mng 전용)
- **API Explorer**: API 엔드포인트 탐색/테스트 도구
- **Flow Tester**: API 호출 시나리오 테스트
- **바로빌 관리**: 세금계산서, 카드매입, 홈택스 연동
- **프로젝트 관리**: PM 도구 (프로젝트, 태스크, 이슈, 일일 로그)
- **영상 관리**: 튜토리얼, Veo3 영상 생성
- **AI 설정**: AI 토큰 사용량, 음성 녹음, 가격 설정
---
## 5. 라우트
| 파일 | 설명 |
|------|------|
| `routes/web.php` | 메인 웹 라우트 (Blade 뷰) |
| `routes/api.php` | 내부 API (mng 전용) |
| `routes/console.php` | Artisan 커맨드 |
---
## 6. 도메인
| 도메인 | URL |
|--------|-----|
| `mng.sam.kr` | 메인 관리자 |
| `admin.sam.kr` | 동일 서버 (nginx alias) |

159
system/overview.md Normal file
View File

@@ -0,0 +1,159 @@
# SAM 시스템 개요
> **최종 갱신**: 2026-02-27
> **상태**: Phase 1-B 작성
---
## 1. 프로젝트 개요
**SAM** (Smart Automation Management) — 블라인드/스크린 제조업체용 ERP/MES 통합 시스템
| 항목 | 내용 |
|------|------|
| 서비스명 | SAM |
| 회사명 | (주)코드브릿지엑스 |
| 아키텍처 | Multi-tenant (tenant_id 기반 데이터 격리) |
| DB | MySQL 8.0 (단일 DB `samdb`, 통계 `sam_stat`) |
| 레거시 | 5130.co.kr (PHP 7.3) → SAM으로 마이그레이션 중 |
---
## 2. 어플리케이션 구조
```
SAM/
├── api/ Laravel 12 REST API (PHP 8.4)
├── mng/ Laravel 12 관리자 패널 (PHP 8.4, HTMX + DaisyUI)
├── react/ Next.js 15 사용자 프론트엔드 (React 19)
├── design/ 디자인 시스템 (Vite, Storybook)
├── 5130/ 레거시 시스템 (PHP 7.3)
├── sales/ 영업자 사이트 (추후 개발)
├── docs/ 기술 문서
├── planning/ 기획 문서
└── docker/ Docker 설정
```
### 앱별 역할
| 앱 | 역할 | 기술 스택 | Git 저장소 |
|----|------|----------|-----------|
| **api** | REST API 서버, DB 마이그레이션 관리 | Laravel 12, PHP 8.4, Sanctum, Swagger | 독립 |
| **mng** | 관리자 패널 (admin.sam.kr 포함) | Laravel 12, PHP 8.4, HTMX, DaisyUI, Vite 7 | 독립 |
| **react** | 사용자 프론트엔드 | Next.js 15, React 19, Tailwind v4, Zustand | 독립 |
| **design** | 디자인 시스템/컴포넌트 | Vite | 독립 |
### 앱 간 관계
```
react (dev.sam.kr) ──API 호출──→ api (api.sam.kr) ←──DB 공유──→ mng (mng.sam.kr)
MySQL (samdb)
5130 (chandj DB)
```
- **api**: DB 마이그레이션 유일 관리자. 모든 테이블 정의는 api에서만.
- **mng**: 자체 모델 보유 (185개), DB 마이그레이션 없음. api와 동일 DB 사용.
- **react**: Server Actions로 api 호출. DB 직접 접근 없음.
---
## 3. 기술 스택 상세
### api/ (REST API)
| 항목 | 상세 |
|------|------|
| Framework | Laravel 12 |
| PHP | 8.4+ |
| 인증 | Sanctum (Access 120분, Refresh 7일) |
| 권한 | Spatie Permission (RBAC) |
| API 문서 | L5-Swagger (OpenAPI) |
| 큐 | Database Driver |
| 캐시 | Database Driver |
| 규모 | 모델 205, 서비스 179, 컨트롤러 131, FormRequest 271 |
| 마이그레이션 | 458개 (메인 437 + 통계 21) |
| API 도메인 | 18개 라우트 파일, ~876 엔드포인트 |
### react/ (프론트엔드)
| 항목 | 상세 |
|------|------|
| Framework | Next.js 15 (App Router, Turbopack) |
| React | 19 |
| 스타일 | Tailwind CSS v4 |
| UI | shadcn/ui (Radix 기반) 55개 컴포넌트 |
| 상태관리 | Zustand 5 (13 stores) |
| 폼 | React Hook Form + Zod |
| i18n | next-intl (ko, en, ja) |
| 차트 | Recharts |
| PDF | jspdf + Puppeteer |
| 규모 | 페이지 249, 컴포넌트 612, Server Actions 91 |
### mng/ (관리자 패널)
| 항목 | 상세 |
|------|------|
| Framework | Laravel 12 (Blade + HTMX) |
| PHP | 8.4+ |
| 프론트 | HTMX 2 + DaisyUI 5 + Tailwind 3 |
| 빌드 | Vite 7 |
| PDF | TCPDF + FPDI |
| Excel | PhpSpreadsheet |
| 규모 | 컨트롤러 171, 블레이드 436, 모델 185, 서비스 98 |
---
## 4. 핵심 아키텍처 패턴
### Multi-tenancy
- 모든 테넌트 데이터는 `tenant_id`로 격리
- `BelongsToTenant` 트레이트/스코프 필수
- 글로벌 테이블 (users, plans 등)은 예외
### Service-First
- 비즈니스 로직은 반드시 Service 클래스에
- Controller는 라우팅 + FormRequest 검증만 담당
- `ApiResponse::handle()` 통일 응답 포맷
### API 인증
- 글로벌: API Key (모든 요청)
- 사용자: Sanctum Bearer Token
- 내부: HMAC (mng ↔ api 내부 통신)
### FormRequest
- Controller에서 직접 검증 금지
- 모든 입력 검증은 FormRequest 클래스에서
---
## 5. 환경 구성
| 환경 | 도메인 | 위치 | 비고 |
|------|--------|------|------|
| **로컬** | *.sam.kr | Docker (macOS) | 개발용 |
| **개발** | codebridge-x.com | 114.203.209.83 | react만 |
| **스테이지+운영** | TBD | 211.117.60.189 | api, mng, react |
### 로컬 도메인
| 도메인 | 서비스 | 포트 |
|--------|--------|------|
| `dev.sam.kr` | react (Next.js) | 3000 |
| `api.sam.kr` | api (Laravel) | 9000 (FastCGI) |
| `mng.sam.kr` | mng (Laravel) | 9000 (FastCGI) |
| `admin.sam.kr` | mng (동일) | 9000 (FastCGI) |
| `design.sam.kr` | design (Vite) | 3002 |
| `5130.sam.kr` | 5130 (레거시) | 9000 (FastCGI) |
---
## 관련 문서
- **DB 스키마**: [database/README.md](database/README.md)
- **API 구조**: [api-structure.md](api-structure.md)
- **React 구조**: [react-structure.md](react-structure.md)
- **MNG 구조**: [mng-structure.md](mng-structure.md)
- **Docker 설정**: [docker-setup.md](docker-setup.md)
- **보안 정책**: [security-policy.md](security-policy.md)

140
system/react-structure.md Normal file
View File

@@ -0,0 +1,140 @@
# React 프론트엔드 구조 현황
> **최종 갱신**: 2026-02-27
> **기술 스택**: Next.js 15 + React 19 + Tailwind v4 + Zustand 5
---
## 1. 프로젝트 규모
| 항목 | 수량 |
|------|------|
| 페이지 (page.tsx) | 249 |
| Server Actions (actions.ts) | 91 |
| 컴포넌트 (.tsx) | 612 |
| Custom Hooks | 25 |
| Zustand Stores | 13 |
| Lib/유틸리티 | 70 |
| TypeScript 타입 | 7 |
| React Context | 6 |
| i18n 언어 | 3 (ko, en, ja) |
---
## 2. 디렉토리 구조
```
src/
├── app/[locale]/
│ ├── (protected)/ 인증 필요 라우트
│ │ ├── accounting/ 회계 (32p)
│ │ ├── approval/ 전자결재 (5p)
│ │ ├── board/ 게시판 (12p)
│ │ ├── company-info/ 회사정보
│ │ ├── construction/ 시공 (57p)
│ │ ├── customer-center/ 고객센터 (10p)
│ │ ├── dashboard/ 대시보드 (5p)
│ │ ├── hr/ 인사 (16p)
│ │ ├── master-data/ 기준정보 (14p)
│ │ ├── material/ 자재 (6p)
│ │ ├── outbound/ 출고 (7p)
│ │ ├── production/ 생산 (12p)
│ │ ├── quality/ 품질 (6p)
│ │ ├── reports/ 리포트 (2p)
│ │ ├── sales/ 영업 (20p)
│ │ ├── settings/ 설정 (20p)
│ │ └── vehicle-management/ 차량 (12p)
│ ├── login/ 로그인 (공개)
│ └── signup/ 회원가입 (공개)
├── components/
│ ├── ui/ shadcn/ui 프리미티브 (55)
│ ├── atoms/ Atomic Design - 원자 (3)
│ ├── molecules/ Atomic Design - 분자 (11)
│ ├── organisms/ Atomic Design - 유기체 (12)
│ ├── templates/ 페이지 템플릿 (13)
│ └── [domain]/ 도메인별 컴포넌트 (~532)
├── hooks/ 커스텀 훅 (25)
├── stores/ Zustand 스토어 (13)
├── lib/ 유틸리티, API 레이어 (70)
├── types/ TypeScript 타입 (7)
├── contexts/ React Context (6)
├── constants/ 상수
├── i18n/ next-intl 설정
├── layouts/ 레이아웃
├── messages/ i18n JSON (ko, en, ja)
└── middleware.ts 인증 + i18n 미들웨어
```
---
## 3. 도메인별 페이지 수
| 도메인 | 페이지 | 주요 기능 |
|--------|--------|----------|
| construction | 57 | 시공관리, 계약, 인수인계, 구조검토 |
| accounting | 32 | 매입/매출, 급여, 대출, 경비 |
| sales | 20 | 견적, 수주, 납품, 입찰, 단가 |
| settings | 20 | 사용자, 권한, 메뉴, 기본설정 |
| hr | 16 | 직원, 근태, 휴가, 급여 |
| master-data | 14 | 품목, BOM, 분류 |
| board | 12 | 게시판, 공지, 자유게시판 |
| vehicle-management | 12 | 법인차량, 운행일지, 정비 |
| production | 12 | 작업지시, 공정, 검사 |
| customer-center | 10 | 거래처, 현장, 브리핑 |
| outbound | 7 | 출고, 물류 |
| quality | 6 | 수입검사, LOT, 품질 |
| material | 6 | 자재입고, 재고 |
| dashboard | 5 | 대시보드 (4종) |
| approval | 5 | 전자결재 |
---
## 4. 아키텍처 패턴
### API 연동
- **Server Actions** (`actions.ts`): 페이지와 co-located
- 공유 유틸: `executeServerAction()`, `executePaginatedAction()`, `buildApiUrl()`
- 모든 API 호출은 서버 사이드에서 실행 (클라이언트 직접 호출 없음)
### 인증
- HttpOnly 쿠키 기반 (Next.js API Route를 통한 프록시)
- `NEXT_PUBLIC_AUTH_MODE=sanctum`
- 모든 페이지 `'use client'` (Server Component 미사용)
### 상태관리
- **글로벌**: Zustand (13 stores) + Immer
- **폼**: React Hook Form + Zod
- **서버 상태**: Server Actions (SWR/React Query 미사용)
### 컴포넌트 아키텍처
- **Atomic Design** 적용: atoms → molecules → organisms → templates → pages
- **shadcn/ui** 55개 프리미티브 컴포넌트
- **도메인별 폴더**: `components/[domain]/` (~532개)
### 주요 의존성
| 패키지 | 버전 | 용도 |
|--------|------|------|
| next | 15.5.9 | 프레임워크 |
| react | 19.2.3 | UI |
| tailwindcss | 4.x | 스타일 |
| zustand | 5.0.9 | 상태관리 |
| zod | 4.1.12 | 스키마 검증 |
| react-hook-form | 7.66.0 | 폼 |
| recharts | 3.4.1 | 차트 |
| next-intl | 4.4.0 | i18n |
| @tiptap/* | - | 리치 텍스트 |
| @capacitor/core | 8.0.0 | 모바일 |
---
## 5. 빌드 설정
| 설정 | 값 |
|------|------|
| Turbopack | 활성화 |
| reactStrictMode | false (중복 API 요청 방지) |
| serverActions.bodySizeLimit | 10mb |
| Puppeteer | serverExternalPackages로 분리 |
| PostCSS | @tailwindcss/postcss (v4 방식) |
| Path Alias | `@/*``./src/*` |

248
system/remote-work-setup.md Normal file
View File

@@ -0,0 +1,248 @@
# 다른 장소에서 작업 환경 구축 가이드
> ⚠️ **DEPRECATED**: 이 문서는 2025-09-19에 작성되었으며, 현재 프로젝트 구조와 맞지 않습니다.
> 최신 정보는 `docs/system/overview.md` 및 `docs/system/docker-setup.md`를 참조하세요.
**생성일**: 2025-09-19 21:50 KST
**목적**: 다른 장소에서 동일한 개발 환경으로 작업 재개
**상태**: ⚠️ DEPRECATED (2025-12-26)
## 🚀 빠른 시작 (5분 내 완료)
### 1단계: 저장소 최신 동기화
```bash
# 모든 저장소에서 실행
git pull origin develop
# 예상 결과: "Already up to date" 또는 새로운 커밋 다운로드
```
### 2단계: Docker 서비스 실행
```bash
# Docker 데스크톱 실행 또는
docker-compose up -d
# 확인
docker ps # MySQL, Redis 등 실행 확인
```
### 3단계: API 서버 상태 확인
```bash
cd api
php artisan migrate:status # DB 상태 확인
php artisan serve # 개발 서버 실행
```
### 4단계: 환경 검증
- 브라우저에서 `http://localhost:8000` 접속 확인
- 데이터베이스 연결 상태 확인
## 📋 상세 환경 구축 절차
### Git 저장소 상태 확인
```bash
# 각 저장소에서 실행
cd /path/to/SAM/api
git status && git log --oneline -3
cd /path/to/SAM/front/www
git status && git log --oneline -3
cd /path/to/SAM/admin
git status && git log --oneline -3
cd /path/to/SAM/shared
git status && git log --oneline -3
```
**기대값**:
- **API**: `3f30c5d` - 프로젝트 체크포인트 및 완전한 문서화 시스템 구축
- **Frontend**: `ec18d70` - 화면 생성 - 수주관리
- **Admin**: `0624422` - 빈디렉토리 설정
- **Shared**: `015b3dc` - Filament BOARD, TENANT 추가
### 환경 파일 확인
```bash
cd api
ls -la .env # API 환경 설정
cat .env | grep DB_ # 데이터베이스 설정 확인
cd ../admin
ls -la .env # Admin 환경 설정
cd ../front/www
ls -la application/config/database.php # CodeIgniter DB 설정
```
### 데이터베이스 상태 검증
```bash
cd api
php artisan migrate:status
# 예상 결과: Batch 11까지 실행됨
# 최종 마이그레이션: 2025_09_11_000100_create_audit_logs_table
```
### 의존성 설치 (필요시)
```bash
# API 저장소
cd api
composer install # PHP 의존성
npm install # Node.js 의존성
# Admin 저장소
cd ../admin
composer install
npm install
# Frontend 저장소
cd ../front/www
composer install
```
## 🔍 문제 해결 가이드
### Docker 연결 문제
```bash
# Docker 상태 확인
docker ps
# MySQL 컨테이너 재시작
docker-compose restart mysql
# 전체 재시작
docker-compose down && docker-compose up -d
```
### 데이터베이스 연결 실패
```bash
# 연결 테스트
cd api
php artisan tinker
> DB::select('SHOW DATABASES');
# 마이그레이션 재실행 (필요시)
php artisan migrate
```
### Git 동기화 문제
```bash
# 충돌 발생시
git stash # 로컬 변경사항 저장
git pull origin develop # 최신 코드 받기
git stash pop # 로컬 변경사항 복원
# 강제 동기화 (주의: 로컬 변경사항 손실)
git reset --hard origin/develop
```
### 권한 문제 (macOS/Linux)
```bash
# 로그 디렉토리 권한
chmod -R 775 storage/
chmod -R 775 bootstrap/cache/
# Composer 캐시 정리
composer clear-cache
```
## 📄 참조 문서들
### 프로젝트 가이드
- **`CLAUDE.md`**: 전체 프로젝트 구조 및 워크플로우
- **`CURRENT_WORKS.md`**: 최근 작업 현황 및 변경사항
- **`CHECKPOINT_2025-09-19.md`**: 복원 지점 및 복구 방법
- **`DATABASE_SCHEMA_2025-09-19.md`**: DB 스키마 상세 분석
### 개발 명령어
```bash
# API 개발 서버 실행
cd api && php artisan serve
# 프론트엔드 개발 서버
cd front/www && php -S localhost:8080
# Admin 개발 서버
cd admin && php artisan serve --port=8001
# 전체 서비스 실행 (API)
cd api && composer dev # Laravel + Queue + Log viewer + Vite
```
## ⚡ 성능 최적화 팁
### IDE 설정
```bash
# PHPStorm
cd api
php artisan ide-helper:generate
php artisan ide-helper:models
# VS Code
# - PHP Intelephense 확장 설치
# - Laravel Extension Pack 설치
```
### 개발 도구
```bash
# API 문서 확인
open http://localhost:8000/api-docs
# 로그 실시간 모니터링
cd api && php artisan pail --timeout=0
# 큐 워커 실행
cd api && php artisan queue:listen --tries=1
```
## 🔄 작업 완료 후 동기화
### 세션 종료 시
```bash
# 1. 모든 변경사항 커밋
git add . && git commit -m "작업 내용 설명"
# 2. 원격 저장소에 푸시
git push origin develop
# 3. 작업 내용 문서화
# CURRENT_WORKS.md 업데이트
# 4. 임시 파일 정리
find . -name ".DS_Store" -delete
rm -f storage/logs/laravel.log
```
### 체크포인트 생성 (중요 작업 후)
```bash
# 새로운 체크포인트 파일 생성
cp CHECKPOINT_2025-09-19.md CHECKPOINT_$(date +%Y-%m-%d).md
# 현재 상태로 업데이트
# - Git 커밋 해시 업데이트
# - 마이그레이션 상태 업데이트
# - 변경사항 문서화
```
## 🆘 긴급 복원 (문제 발생시)
### 완전 복원
```bash
# CHECKPOINT_2025-09-19.md 파일 참조
# 1. 데이터베이스 롤백
cd api && php artisan migrate:rollback --step=7
# 2. Git 리셋
git reset --hard 3f30c5d # API
cd ../front/www && git reset --hard ec18d70
cd ../../admin && git reset --hard 0624422
cd ../shared && git reset --hard 015b3dc
# 3. 마이그레이션 재실행
cd ../api && php artisan migrate
```
---
**가이드 작성**: Claude Code
**검증 완료**: ✅ 모든 단계 테스트됨
**업데이트**: 새로운 환경에서 문제 발생시 이 문서 개선 필요

214
system/scaling-roadmap.md Normal file
View File

@@ -0,0 +1,214 @@
# SAM 10,000 테넌트 스케일링 로드맵
> **작성일**: 2026-02-22
> **성격**: 가상 시나리오 — 세계 최고 수준 엔지니어링 팀이 설계한다는 가정
---
## 1. 현재 상태 진단
### 1.1 현재 아키텍처 요약
```
브라우저 → Nginx → PHP-FPM(20 workers) → Laravel → MySQL 8.0 (단일)
서버: 2코어/3.8GB RAM | 배포: 수동 git pull | 모니터링: 없음 | 캐시: 없음
```
### 1.2 핵심 병목 지점
| 영역 | 현재 | 10,000 테넌트 시 문제 | 심각도 |
|------|------|---------------------|--------|
| DB | MySQL 단일 | 커넥션 폭발, 슬로우 쿼리 | 치명적 |
| 컴퓨팅 | FPM 20워커 | 동시 요청 20개 제한 | 치명적 |
| 캐시 | 없음 | 모든 요청이 DB 직행 | 치명적 |
| 큐 | DB 드라이버 | 큐 자체가 DB 압박 | 심각 |
| 검색 | SQL LIKE | 219개 테이블 풀스캔 | 심각 |
| 배포 | 수동 git pull | 다운타임, 롤백 불가 | 높음 |
| 모니터링 | 없음 | 장애 인지 불가 | 높음 |
### 1.3 가장 먼저 죽는 곳
- 동시 사용자 **20명** → FPM 워커 전부 점유 → 504 Timeout
- 동시 사용자 **200명** → MySQL `max_connections`(151) 고갈
- 테넌트 **1,000개** → 권한 UNION 3개 쿼리가 매 요청마다 실행
---
## 2. 5단계 로드맵 개요
```
Phase 1 (0~3개월) 캐시 + 모니터링 + 서버 업그레이드 → 100 테넌트
Phase 2 (3~6개월) DB 복제 + K8s + S3 + 검색 엔진 → 1,000 테넌트
Phase 3 (6~9개월) 마이크로서비스 + 이벤트 아키텍처 → 3,000 테넌트
Phase 4 (9~12개월) DB 샤딩 + 테넌트 티어링 + 멀티리전 → 10,000 테넌트
Phase 5 (12~18개월) 관측성 고도화 + 카오스 엔지니어링 → 10,000+ 테넌트
```
---
## 3. Phase 1: 기초 체력 (0~3개월) → 100 테넌트
### 3.1 Redis 도입 (최우선)
| 대상 | 캐시 TTL | 효과 |
|------|---------|------|
| 세션 저장소 | 2시간 | DB 세션 테이블 부하 제거 |
| 권한 캐시 | 5분 | UNION 3개 쿼리 제거 (매 요청) |
| 메뉴 트리 | 10분 | 중첩 쿼리 제거 |
| 공통 코드 | 1시간 | `common_codes` 반복 조회 제거 |
| Laravel 큐 | - | `database``redis` 드라이버 전환 |
**예상 효과**: DB 쿼리 **60~70% 감소**, 응답 시간 **3~5배 개선**.
### 3.2 모니터링 구축
Grafana + Prometheus + Laravel Telescope + MySQL slow_log.
**핵심 알림**: FPM 워커 사용률 >80%, MySQL 커넥션 >100, 응답 시간 >2초, 큐 적체 >1000건 → Slack 알림.
### 3.3 서버 업그레이드 + CI/CD
| 항목 | 현재 | Phase 1 |
|------|------|---------|
| CPU/RAM | 2코어/3.8GB | 8코어/32GB |
| 스토리지 | HDD | NVMe SSD |
| FPM | 20 워커 | 100 워커 |
| 배포 | 수동 git pull | Jenkins CI/CD (무중단 rolling) |
---
## 4. Phase 2: 수평 확장 (3~6개월) → 1,000 테넌트
### 4.1 아키텍처
```
브라우저 → LB(L7) → App Server ×N → Redis Cluster
→ MySQL Primary + Replica ×2
→ S3 + CDN (정적/업로드 파일)
```
### 4.2 핵심 변경
| 영역 | 변경 | 효과 |
|------|------|------|
| **K8s** | HPA 기반 오토스케일링 (API 3~10 pods) | 트래픽에 따라 자동 확장 |
| **DB R/W 분리** | `config/database.php`에 read/write 설정 | 읽기 부하 80% Replica로 분산 |
| **파일 → S3** | Laravel Filesystem → S3 + CloudFront | 서버 간 파일 공유, CDN 가속 |
| **검색 엔진** | SQL LIKE → Meilisearch | 밀리초 응답, 형태소 분석, 오타 허용 |
---
## 5. Phase 3: 마이크로서비스 (6~9개월) → 3,000 테넌트
### 5.1 서비스 분리
```
모놀리스(sam-api) → Auth | Product | Order | MES | Finance | Notification
```
**분리 순서**: ① 알림 (독립적, 비동기) → ② MES (변경 빈도 높음) → ③ 인증 (가용성 최우선)
### 5.2 이벤트 기반 전환
동기 호출 체인을 이벤트 발행으로 전환한다. 주문 생성 시 재고 차감/알림/회계를 비동기 처리.
**메시지 브로커**: Redis Streams 또는 RabbitMQ. 응답 시간 2초 → 200ms.
### 5.3 API Gateway
Kong/Traefik으로 테넌트별 Rate Limiting, 인증 검증, 요청 라우팅, 응답 캐싱 통합.
---
## 6. Phase 4: 대규모 멀티테넌시 (9~12개월) → 10,000 테넌트
### 6.1 DB 샤딩
```
Shard Router (tenant_id 기반) → Shard 0 | Shard 1 | Shard 2 ...
각 Shard = Primary + Replica
```
`tenant_id`가 파티션 키이므로 크로스 샤드 조인 불필요 — SAM의 강점.
### 6.2 테넌트 티어링
| Tier | 자원 | SLA | Rate Limit |
|------|------|-----|-----------|
| Enterprise | 전용 DB/Redis/Pod | 99.99% | 1000/분 |
| Business | 공유 (높은 우선순위) | 99.9% | 300/분 |
| Standard | 공유 | 99.5% | 60/분 |
### 6.3 멀티리전
한국(Primary) + 일본/동남아(Secondary) 리전. DB 복제 + Global Load Balancer로 지리적 라우팅.
---
## 7. Phase 5: 엔터프라이즈 성숙 (12~18개월) → 10,000+
| 영역 | 도입 | 목적 |
|------|------|------|
| **관측성** | Jaeger(분산추적) + Loki(중앙로그) + PagerDuty | 전체 서비스 체인 추적 |
| **카오스 엔지니어링** | DB 다운, Pod Kill, 네트워크 지연 주입 | 복원력 검증 |
| **데이터 파이프라인** | CDC(Debezium) → 데이터 웨어하우스 | 운영 DB에서 분석 쿼리 분리 |
| **배포** | Blue-Green + Canary (5% → 100%) | 30초 이내 롤백 |
---
## 8. 기술 스택 진화 요약
| 영역 | 현재 | Phase 1 | Phase 2 | Phase 4 |
|------|------|---------|---------|---------|
| 서버 | 단일 2코어 | 단일 8코어 | K8s 3+ 노드 | 멀티리전 |
| DB | MySQL 단일 | + 쿼리 최적화 | Primary + Replica ×2 | Shard ×N |
| 캐시 | 없음 | Redis 단일 | Redis Cluster | 테넌트별 격리 |
| 큐 | DB | Redis | Redis | 티어별 큐 |
| 배포 | 수동 | Jenkins CI/CD | K8s Rolling | Canary |
| 모니터링 | 없음 | Grafana | + Telescope | + 카오스 |
---
## 9. 가장 중요한 3가지
**1. Redis (=산소)**: 캐시 없이 10,000 테넌트는 **절대 불가능**. 권한 UNION 쿼리 3개 + 메뉴 + 세션이 매 요청마다 실행된다. Redis 하나로 DB 부하 60% 감소.
**2. DB R/W 분리 (=심장)**: ERP 읽기:쓰기 = 8:2. Replica 2대 추가로 DB 부하 1/3 분산. Laravel `config/database.php` 변경만으로 적용.
**3. 관측성 (=눈)**: 모니터링 없이 스케일링은 눈 감고 운전하는 것. 슬로우 쿼리 + 응답 시간 + 알림부터 시작.
---
## 10. SAM 특수 고려사항
### 10.1 tenant_id 기반 격리
- **강점**: `BelongsToTenant` 스코프 일관 적용, 크로스 테넌트 조인 불필요 → 샤딩에 유리
- **개선 필요**: `tenant_id` 인덱스 첫 번째 컬럼 여부 전수 검사, `deleted_at` 누적 데이터 파티셔닝
### 10.2 권한 시스템 최적화
3중 UNION 쿼리가 매 요청 실행된다. 개선: ① Redis 권한 캐시(TTL 5분) → ② 변경 시 해당 사용자 캐시만 무효화 → ③ JWT 클레임에 권한 포함(DB 조회 0회).
### 10.3 219개 테이블 샤딩 분류
| 분류 | 예시 | 처리 |
|------|------|------|
| 테넌트 데이터 | orders, products | 샤딩 대상 |
| 시스템 공통 | permissions, common_codes | 공유 DB 유지 |
| 감사 로그 | audit_logs | 별도 시계열 DB |
---
## 관련 문서
| 문서 | 설명 |
|------|------|
| [overview.md](overview.md) | 현재 시스템 아키텍처 |
| [security-policy.md](security-policy.md) | 현재 보안 구조 |
| [docker-setup.md](docker-setup.md) | 현재 Docker 구성 |
| [server-how-it-works.md](../guides/server-how-it-works.md) | 서버 동작 원리 |
---
**최종 업데이트**: 2026-02-22

784
system/security-policy.md Normal file
View File

@@ -0,0 +1,784 @@
# SAM API 보안 가이드
## 개요
SAM API는 다층 보안 구조를 통해 무단 접근과 악의적 공격으로부터 시스템을 보호합니다.
**최종 업데이트:** 2025-12-26
---
## 보안 아키텍처
### 다층 방어 구조 (Defense in Depth)
```
┌─────────────────────────────────────────────────┐
│ Layer 1: Nginx (L7 Application Layer) │
│ - 악의적 경로 패턴 차단 │
│ - 의심스러운 User-Agent 차단 │
│ - Rate Limiting (Nginx 레벨) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Layer 2: Laravel Rate Limiting │
│ - IP 기반 속도 제한 (10회/분) │
│ - API Key 없는 요청 차단 │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Layer 3: API Key 검증 (글로벌 미들웨어) │
│ - 모든 요청 API Key 필수 │
│ - 화이트리스트 라우트 제외 │
│ - 보안 로그 자동 기록 │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Layer 4: Sanctum 토큰 인증 │
│ - Bearer 토큰 검증 │
│ - 사용자 컨텍스트 설정 │
│ - 테넌트 격리 │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Layer 5: 권한 검증 (Permission Check) │
│ - 메뉴 기반 권한 체크 │
│ - Role 기반 접근 제어 │
└─────────────────────────────────────────────────┘
```
---
## Layer 1: Nginx 보안
### 악의적 경로 패턴 차단
**위치:** `docker/nginx/nginx.conf`
```nginx
# 경로 탐색 공격 차단
if ($request_uri ~* "(\.\.\/|\.\.\\|etc\/passwd|\.env|\.git|\.htaccess|\.sql|@fs\/)") {
return 403;
}
```
**차단 패턴:**
- `../`, `..\` - 디렉토리 탐색 공격
- `etc/passwd` - 시스템 파일 접근 시도
- `.env` - 환경 변수 파일 접근
- `.git`, `.htaccess`, `.sql` - 민감한 파일 접근
- `@fs/` - Vite 경로 탐색 공격
**응답:** 403 Forbidden
### 의심스러운 User-Agent 차단
```nginx
# 보안 스캔 도구 차단
if ($http_user_agent ~* "(sqlmap|nikto|nmap|masscan|metasploit|nessus)") {
return 403;
}
```
**차단 도구:**
- sqlmap - SQL 인젝션 스캐너
- nikto - 웹 서버 스캐너
- nmap - 포트 스캐너
- masscan - 대량 포트 스캐너
- metasploit - 침투 테스트 프레임워크
- nessus - 취약점 스캐너
**응답:** 403 Forbidden
---
## Layer 2: Rate Limiting
### Laravel Rate Limiter
**위치:** `app/Http/Middleware/ApiRateLimiter.php`
```php
// IP 기반 속도 제한
$key = 'api-key-attempts:' . $request->ip();
if ($this->limiter->tooManyAttempts($key, 10)) {
return response()->json([
'message' => 'Too many attempts. Please try again later.',
'retry_after' => $seconds,
], 429);
}
$this->limiter->hit($key, 60); // 1분 동안 유지
```
**설정:**
- **제한:** 10회/분 (IP별)
- **대상:** API Key 없는 요청
- **유지 시간:** 60초
- **응답 코드:** 429 Too Many Requests
**로그:**
```php
Log::warning('API Rate Limit Exceeded', [
'ip' => $request->ip(),
'uri' => $request->getRequestUri(),
'retry_after' => $seconds,
]);
```
---
## Layer 3: API Key 인증
### 글로벌 미들웨어
**위치:** `bootstrap/app.php`
```php
$middleware->append(ApiRateLimiter::class); // 1. Rate Limiting
$middleware->append(ApiKeyMiddleware::class); // 2. API Key 검증
```
**실행 순서:** Rate Limiting → API Key 검증
### API Key 검증 로직
**위치:** `app/Http/Middleware/ApiKeyMiddleware.php`
```php
// 1. 화이트리스트 체크
$publicRoutes = [
'api/v1/login',
'api/v1/signup',
'api/v1/register',
'api/v1/refresh',
'api/v1/debug-apikey',
'api-docs', // Swagger UI
'api-docs/*', // Swagger 하위 경로
'docs/api-docs.json', // Swagger JSON
'up', // Health check
];
// 2. API Key 검증
$apiKey = $request->header('X-API-KEY');
$validApiKey = DB::table('api_keys')
->where('key', $apiKey)
->where('is_active', true)
->exists();
// 3. 보안 로그 기록
if (!$validApiKey) {
Log::warning('Unauthorized API access attempt', [
'ip' => $request->ip(),
'uri' => $request->getRequestUri(),
'method' => $request->method(),
'user_agent' => $request->userAgent(),
]);
}
```
### 화이트리스트 (인증 제외 라우트)
**공개 엔드포인트:**
- `api/v1/login` - 로그인
- `api/v1/signup` - 회원가입
- `api/v1/register` - 테넌트 등록
- `api/v1/refresh` - 토큰 갱신
- `api/v1/debug-apikey` - API Key 디버깅
- `api-docs/*` - Swagger UI
- `docs/api-docs.json` - Swagger JSON
- `up` - Health check
**특징:**
- 와일드카드 지원 (`fnmatch()` 사용)
- 공개 라우트는 로깅 제외
- API Key 검증 스킵
### 보안 로그
**로그 레벨:**
- **Log::info** - 정상 API 요청
- **Log::warning** - 무단 접근 시도
**로그 내용:**
```json
{
"ip": "213.136.76.215",
"uri": "/@fs/etc/passwd",
"method": "GET",
"user_agent": "Mozilla/5.0 ..."
}
```
**민감 정보 제외:**
- `password`
- `password_confirmation`
---
## Layer 4: Sanctum 토큰 인증
### 토큰 구조
**액세스 토큰:**
- 만료 시간: 2시간 (120분)
- 용도: API 호출 인증
- 형식: `{token_id}|{plain_text_token}`
**리프레시 토큰:**
- 만료 시간: 7일 (10080분)
- 용도: 액세스 토큰 갱신
- 특징: 일회성 사용 (사용 후 삭제)
### 토큰 갱신 플로우
```
1. 클라이언트: POST /api/v1/refresh
Headers: X-API-KEY, Authorization: Bearer {refresh_token}
2. 서버: 리프레시 토큰 검증
- 유효성 체크
- 만료 시간 체크
- 사용자 확인
3. 서버: 기존 리프레시 토큰 삭제
- 일회성 사용 보장
4. 서버: 새 토큰 발급
- 새 액세스 토큰 생성
- 새 리프레시 토큰 생성
5. 응답:
{
"access_token": "...",
"refresh_token": "...",
"expires_in": 7200,
"expires_at": "2025-11-13 21:30:00"
}
```
### 토큰 만료 에러 처리
**위치:** `app/Exceptions/Handler.php`
```php
if ($exception instanceof AuthenticationException) {
$bearerToken = $request->bearerToken();
if ($bearerToken) {
$token = PersonalAccessToken::findToken($bearerToken);
if ($token && $token->expires_at && $token->expires_at->isPast()) {
return response()->json([
'success' => false,
'message' => __('error.token_expired'),
'error_code' => 'TOKEN_EXPIRED',
], 401);
}
}
}
```
**프론트엔드 처리:**
```javascript
if (response.error_code === 'TOKEN_EXPIRED') {
// 자동 리프레시 토큰으로 재발급
const newTokens = await refreshToken(refreshToken);
// 원래 요청 재시도
return retryRequest(originalRequest, newTokens.access_token);
}
```
---
## Layer 5: 권한 검증 (Permission System)
### 권한 시스템 개요
SAM은 **Spatie Permission** 패키지를 기반으로 한 다층 권한 시스템을 사용합니다.
**3단계 권한 구조:**
```
┌────────────────────────────────────────┐
│ 1. 사용자 역할 권한 │
│ User → Role → Permissions │
│ (model_has_roles → role_has_perms) │
└────────────────────────────────────────┘
+
┌────────────────────────────────────────┐
│ 2. 사용자 직접 권한 │
│ User → Permissions │
│ (model_has_permissions) │
└────────────────────────────────────────┘
+
┌────────────────────────────────────────┐
│ 3. 부서 역할 권한 │
│ User → Department → Role → Perms │
│ (department_user → model_has_roles) │
└────────────────────────────────────────┘
┌────────────────────────────────────────┐
│ UNION (중복 제거) │
│ → 최종 사용자 권한 목록 │
└────────────────────────────────────────┘
```
**특징:**
- 멀티테넌트 지원 (tenant_id로 격리)
- 메뉴 기반 세분화된 권한
- 다형성(Polymorphic) 구조로 유연한 확장
- Permission Override로 임시/긴급 권한 제어
---
### 권한 패턴 및 타입
**권한 명명 규칙:**
```
menu:{menu_id}.{permission_type}
```
**권한 타입 (7가지):**
| 타입 | 약자 | 설명 | 예시 |
|------|------|------|------|
| view | V | 조회 | 목록/상세 보기 |
| create | C | 생성 | 신규 데이터 등록 |
| update | U | 수정 | 기존 데이터 편집 |
| delete | D | 삭제 | 데이터 삭제 |
| approve | A | 승인 | 워크플로우 승인 |
| export | E | 내보내기 | Excel/PDF 다운로드 |
| manage | M | 관리 | 전체 관리 권한 |
**권한 예시:**
```
menu:1.view → 대시보드 보기
menu:2.create → 제품 생성
menu:2.update → 제품 수정
menu:2.delete → 제품 삭제
menu:3.approve → 주문 승인
menu:4.export → 재고 내보내기
menu:5.manage → 사용자 관리
```
---
### 권한 조회 로직
**구현 위치:**
- **API:** `app/Services/MemberService.php` - `getUserInfoForLogin()`
- **Admin:** `app/Filament/Resources/Users/Tables/UsersTable.php` - `getAccessibleMenusCount()`
**권한 조회 쿼리 구조:**
```php
// 1. 사용자 역할 권한
$userRolePermissions = DB::table('model_has_roles')
->join('role_has_permissions', 'model_has_roles.role_id', '=', 'role_has_permissions.role_id')
->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
->where('model_has_roles.model_type', User::class)
->where('model_has_roles.model_id', $userId)
->where('model_has_roles.tenant_id', $tenantId)
->where('permissions.name', 'like', 'menu:%.view')
->select('permissions.name');
// 2. 사용자 직접 권한
$userDirectPermissions = DB::table('model_has_permissions')
->join('permissions', 'model_has_permissions.permission_id', '=', 'permissions.id')
->where('model_has_permissions.model_type', User::class)
->where('model_has_permissions.model_id', $userId)
->where('model_has_permissions.tenant_id', $tenantId)
->where('permissions.name', 'like', 'menu:%.view')
->select('permissions.name');
// 3. 부서 역할 권한
$departmentRolePermissions = DB::table('department_user')
->join('model_has_roles', function ($join) {
$join->on('department_user.department_id', '=', 'model_has_roles.model_id')
->where('model_has_roles.model_type', '=', Department::class);
})
->join('role_has_permissions', 'model_has_roles.role_id', '=', 'role_has_permissions.role_id')
->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
->where('department_user.user_id', $userId)
->where('department_user.tenant_id', $tenantId)
->where('permissions.name', 'like', 'menu:%.view')
->select('permissions.name');
// 4. 모든 권한 통합 (UNION + 중복 제거)
$allPermissions = $userRolePermissions
->union($userDirectPermissions)
->union($departmentRolePermissions)
->pluck('name')
->toArray();
```
**권한 파싱:**
```php
// menu:123.view → 메뉴 ID 123 추출
foreach ($allPermissions as $permName) {
if (preg_match('/^menu:(\d+)\.view$/', $permName, $matches)) {
$allowedMenuIds[] = (int) $matches[1];
}
}
```
---
### Permission Override (우선순위 제어)
**시간 기반 권한 제어:**
```php
// permission_overrides 테이블
[
'tenant_id' => 1,
'model_type' => 'App\Models\Members\User',
'model_id' => 123,
'permission_id' => 456,
'effect' => 1, // 1=ALLOW, -1=DENY
'effective_from' => '2025-11-13 00:00:00',
'effective_to' => '2025-11-20 23:59:59',
]
```
**우선순위:**
1. **Override DENY** (-1) - 최우선 차단
2. **Override ALLOW** (1) - 명시적 허용
3. **Base Permission** - 역할/부서/직접 권한
**최종 권한 계산:**
```php
foreach ($allMenuPermissions as $permName) {
if (preg_match('/^menu:(\d+)\.view$/', $permName, $matches)) {
$menuId = (int) $matches[1];
// Override DENY 체크 (강제 차단)
if (isset($overrides[$permName]) && $overrides[$permName]->effect === -1) {
continue; // 이 메뉴는 차단됨
}
// Override ALLOW 또는 기본 권한
if (
(isset($overrides[$permName]) && $overrides[$permName]->effect === 1) ||
in_array($permName, $basePermissions, true)
) {
$allowedMenuIds[] = $menuId;
}
}
}
```
**사용 사례:**
- **임시 권한 부여:** 프로젝트 기간 동안만 특정 메뉴 접근 허용
- **긴급 권한 차단:** 보안 사고 발생 시 즉시 권한 제거
- **휴가 기간 제한:** 특정 기간 동안 권한 자동 차단
- **시간대별 접근 제어:** 업무 시간에만 권한 부여
---
### 메뉴별 권한 매트릭스 뷰
**Admin 패널:** `http://admin.sam.kr/admin/permissions`
**테이블 구조:**
| 메뉴 ID | 메뉴명 | V (조회) | C (생성) | U (수정) | D (삭제) | A (승인) | E (내보내기) | M (관리) |
|---------|--------|----------|----------|----------|----------|----------|-------------|----------|
| 1 | 대시보드 | 홍길동, 김철수 | - | - | - | - | - | - |
| 2 | 제품 관리 | 홍길동, 김철수 | 홍길동 | 홍길동 | 관리자 | - | 김철수 | 관리자 |
| 3 | 주문 관리 | 전체팀 | 영업팀 | 영업팀 | 관리자 | 관리자 | 회계팀 | 관리자 |
**특징:**
- 각 Row = 하나의 메뉴
- 각 권한 타입별 Column에 해당 권한을 가진 사용자 목록 표시
- 사용자 배지에 마우스 오버 시 `user_id` 툴팁 표시
- 권한 없는 경우 `-` 표시
- 3가지 권한 소스 (역할/부서/직접) 모두 통합하여 표시
**구현 코드:**
```php
// app/Filament/Resources/Permissions/Tables/PermissionsTable.php
protected static function getUsersWithPermission(int $menuId, string $permissionType): string
{
$permissionName = "menu:{$menuId}.{$permissionType}";
// 3가지 권한 소스 UNION
$userIds = $userRoleQuery
->union($userDirectQuery)
->union($departmentRoleQuery)
->pluck('user_id')
->unique()
->toArray();
// 사용자 배지 HTML 생성
$users = User::whereIn('id', $userIds)->orderBy('name')->get();
foreach ($users as $user) {
$badges[] = sprintf(
'<span title="%s">%s</span>',
htmlspecialchars($user->user_id),
htmlspecialchars($user->name)
);
}
return implode(', ', $badges);
}
```
---
### 권한 할당 방법
**1. 역할에 권한 할당:**
```php
$role = Role::findByName('영업팀', 'web');
$role->givePermissionTo([
'menu:2.view',
'menu:2.create',
'menu:3.view',
]);
```
**2. 사용자에게 직접 권한 할당:**
```php
$user = User::find(123);
$user->givePermissionTo('menu:5.manage');
```
**3. 부서에 역할 할당:**
```php
$department = Department::find(1);
$department->assignRole('영업팀');
```
**4. Permission Override 설정:**
```php
DB::table('permission_overrides')->insert([
'tenant_id' => 1,
'model_type' => User::class,
'model_id' => 123,
'permission_id' => 456,
'effect' => -1, // DENY
'effective_from' => now(),
'effective_to' => now()->addDays(7),
]);
```
---
### 권한 체크 (Controller/Service)
**CheckPermission Middleware:**
```php
// routes/api.php
Route::get('/products', [ProductController::class, 'index'])
->middleware(['auth:sanctum', 'permission:menu:2.view']);
```
**서비스 레이어:**
```php
if (!auth()->user()->can('menu:2.create')) {
throw new \Exception(__('error.permission_denied'), 403);
}
```
**권한 확인 헬퍼:**
```php
// 단일 권한 체크
auth()->user()->can('menu:2.view');
// 여러 권한 중 하나라도 있으면
auth()->user()->hasAnyPermission(['menu:2.view', 'menu:2.manage']);
// 모든 권한 필요
auth()->user()->hasAllPermissions(['menu:2.view', 'menu:2.create']);
```
---
## 보안 모니터링
### 로그 파일
**1. 보안 로그**
```bash
# 무단 접근 시도
tail -f storage/logs/laravel.log | grep "Unauthorized API access attempt"
# Rate Limit 초과
tail -f storage/logs/laravel.log | grep "API Rate Limit Exceeded"
```
**2. Nginx 로그**
```bash
# 접근 로그
tail -f /var/log/nginx/api.sam.kr_access.log
# 에러 로그 (403 차단)
tail -f /var/log/nginx/api.sam.kr_error.log
```
### 공격 패턴 분석
**자주 발생하는 공격:**
1. **경로 탐색 (Path Traversal)**
```
GET /@fs/etc/passwd
GET /../../../etc/passwd
GET /api/../.env
```
**대응:** Nginx에서 403 차단
2. **보안 스캔**
```
User-Agent: sqlmap/1.0
User-Agent: nikto/2.1.5
```
**대응:** Nginx User-Agent 필터링
3. **무차별 대입 (Brute Force)**
```
POST /api/v1/login (반복)
```
**대응:** Rate Limiting (10회/분)
4. **API Key 누락**
```
GET /api/v1/users (X-API-KEY 없음)
```
**대응:** 401 Unauthorized + 보안 로그
---
## 보안 체크리스트
### 개발 시
- [ ] 모든 API 엔드포인트에 `auth.apikey` 미들웨어 적용
- [ ] 민감한 정보 로깅 제외 (`password`, `password_confirmation`)
- [ ] FormRequest로 입력 검증
- [ ] SQL 인젝션 방지 (Eloquent ORM 사용)
- [ ] XSS 방지 (출력 시 이스케이핑)
- [ ] CSRF 보호 (Sanctum 자동 적용)
### 배포 전
- [ ] `.env` 파일 보안 설정 확인
- [ ] API Key 로테이션
- [ ] Nginx 보안 규칙 테스트
- [ ] Rate Limiting 임계값 검토
- [ ] HTTPS 인증서 유효성 확인
- [ ] 방화벽 규칙 설정
### 운영 중
- [ ] 매일 보안 로그 검토
- [ ] 주간 공격 패턴 분석
- [ ] 월간 토큰 만료 정책 검토
- [ ] 분기별 API Key 갱신
- [ ] 반기별 침투 테스트
---
## 보안 사고 대응
### 1단계: 즉시 조치
```bash
# 1. 의심스러운 IP 차단 (Nginx)
# /etc/nginx/conf.d/blocked_ips.conf
deny 213.136.76.215;
# 2. Nginx 재시작
sudo systemctl reload nginx
# 3. 활성 세션 강제 종료
php artisan sanctum:prune-expired --hours=0
# 4. API Key 비활성화
UPDATE api_keys SET is_active = 0 WHERE key = 'suspicious_key';
```
### 2단계: 로그 분석
```bash
# 공격 패턴 분석
grep "213.136.76.215" /var/log/nginx/api.sam.kr_access.log
# 영향받은 엔드포인트 확인
grep "Unauthorized API access attempt" storage/logs/laravel.log | grep "213.136.76.215"
# 시간대별 요청 횟수
awk '{print $4}' /var/log/nginx/api.sam.kr_access.log | cut -d: -f1-2 | uniq -c
```
### 3단계: 복구 및 강화
```bash
# 1. 모든 사용자 비밀번호 초기화 (필요 시)
# 2. 새 API Key 발급
# 3. 토큰 만료 시간 단축 (임시)
# 4. Rate Limiting 임계값 강화
# 5. 추가 보안 규칙 적용
```
---
## FAQ
### Q1. API Key는 어디서 발급받나요?
**A:** 관리자 패널(admin.sam.kr)에서 발급합니다.
```sql
-- api_keys 테이블 구조
id, tenant_id, name, key, is_active, created_at, updated_at
```
### Q2. Rate Limiting이 너무 엄격해요.
**A:** `ApiRateLimiter.php`에서 임계값 조정:
```php
if ($this->limiter->tooManyAttempts($key, 10)) { // 10 → 20으로 변경
```
### Q3. 화이트리스트에 라우트를 추가하려면?
**A:** `ApiKeyMiddleware.php` 수정:
```php
$publicRoutes = [
// 기존 라우트...
'api/v1/public-data', // 추가
];
```
### Q4. 특정 IP만 허용하려면?
**A:** Nginx 설정 추가:
```nginx
# 화이트리스트 IP만 허용
allow 203.0.113.0/24;
allow 198.51.100.50;
deny all;
```
### Q5. 토큰 만료 시간을 변경하려면?
**A:** `.env` 파일 수정:
```env
SANCTUM_ACCESS_TOKEN_EXPIRATION=120 # 2시간 → 4시간 (240)
SANCTUM_REFRESH_TOKEN_EXPIRATION=10080 # 7일 → 14일 (20160)
```
---
## 참고 문서
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [Laravel Security Best Practices](https://laravel.com/docs/12.x/security)
- [Sanctum Documentation](https://laravel.com/docs/12.x/sanctum)
- [Nginx Security Tips](https://nginx.org/en/docs/http/ngx_http_access_module.html)
---
**작성일:** 2025-12-26
**버전:** 1.0
**담당자:** SAM Development Team