# SAM ERP API 개발 명세서

> 기준 문서: SAM_ERP_Storyboard_D0.8_251216
> 작성일: 2025-12-17
> 상태: 개발 준비 완료

---

## 개발 범위 요약

| 구분 | 항목수 | 작업 |
|------|--------|------|
| 기존 API 활용 | 12개 | 프론트엔드 연동만 진행 |
| 확장 개발 | 6개 | 기존 구조 활용, API 추가 |
| 신규 개발 | 8개 | 테이블 + API 신규 생성 |

---

# Part 1: 기존 API (프론트엔드 연동)

> 아래 영역은 API 개발 완료. 프론트엔드 연동 시 참고.

| 영역 | 기존 API | 스토리보드 참조 |
|------|----------|-----------------|
| 사원관리 | `/v1/employees/*` | 슬라이드 28-35 |
| 부서관리 | `/v1/departments/*` | 슬라이드 36-38 |
| 근태관리 | `/v1/attendances/*` | 슬라이드 23-27, 39-42 |
| 게시판 | `/v1/boards/*` | - |
| 권한관리 | `/v1/roles/*`, `/v1/permissions/*` | 슬라이드 96 |
| 테넌트 | `/v1/tenants/*` | 슬라이드 14-22 |
| 메뉴 | `/v1/menus/*` | 슬라이드 8-13 |
| 거래처 | `/v1/clients/*` | 슬라이드 60-67 |
| 공통코드 | `/v1/settings/common/*` | 슬라이드 93-95 |
| 파일 | `/v1/files/*` | - |
| 설정 | `/v1/settings/*` | - |
| 프로필 | `/v1/users/me/*`, `/v1/profiles/*` | - |

---

# Part 2: 확장 개발 (기존 구조 활용)

## 2.1 휴가 관리

### 스펙
- 스토리보드: 슬라이드 43-46, 100
- 의존성: `attendances` 테이블 참조

### 테이블: `leaves`
```sql
CREATE TABLE leaves (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    leave_type VARCHAR(20) NOT NULL COMMENT '연차/반차/병가/경조사/출산/육아',
    start_date DATE NOT NULL,
    end_date DATE NOT NULL,
    days DECIMAL(3,1) NOT NULL COMMENT '사용일수',
    reason TEXT,
    status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/approved/rejected/cancelled',
    approved_by BIGINT NULL,
    approved_at TIMESTAMP NULL,
    reject_reason TEXT NULL,
    created_by BIGINT,
    updated_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_tenant_user (tenant_id, user_id),
    INDEX idx_status (status),
    INDEX idx_dates (start_date, end_date)
);
```

### 테이블: `leave_balances`
```sql
CREATE TABLE leave_balances (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    year INT NOT NULL,
    total_days DECIMAL(4,1) NOT NULL DEFAULT 15 COMMENT '연간 부여일수',
    used_days DECIMAL(4,1) NOT NULL DEFAULT 0 COMMENT '사용일수',
    remaining_days DECIMAL(4,1) GENERATED ALWAYS AS (total_days - used_days) STORED,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_tenant_user_year (tenant_id, user_id, year)
);
```

### API 엔드포인트
```
GET    /v1/leaves                   # 목록 (필터: status, user_id, date_range)
POST   /v1/leaves                   # 신청
GET    /v1/leaves/{id}              # 상세
PATCH  /v1/leaves/{id}              # 수정 (pending 상태만)
DELETE /v1/leaves/{id}              # 취소 (pending 상태만)
POST   /v1/leaves/{id}/approve      # 승인
POST   /v1/leaves/{id}/reject       # 반려 (reject_reason 필수)

GET    /v1/leaves/balance           # 내 잔여휴가
GET    /v1/leaves/balance/{userId}  # 특정 사용자 잔여휴가

GET    /v1/settings/leave           # 휴가 설정
PUT    /v1/settings/leave           # 휴가 설정 수정
```

### Request/Response 예시
```json
// POST /v1/leaves
{
    "leave_type": "annual",
    "start_date": "2025-01-20",
    "end_date": "2025-01-21",
    "days": 2,
    "reason": "개인 사유"
}

// GET /v1/leaves/balance
{
    "year": 2025,
    "total_days": 15,
    "used_days": 3,
    "remaining_days": 12
}
```

---

## 2.2 근무/출퇴근 설정

### 스펙
- 스토리보드: 슬라이드 97-99
- 의존성: `tenant_field_settings` 활용 가능

### 테이블: `work_settings`
```sql
CREATE TABLE work_settings (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL UNIQUE,
    work_type VARCHAR(20) DEFAULT 'fixed' COMMENT 'fixed/flexible/custom',
    standard_hours INT DEFAULT 40 COMMENT '주당 소정근로시간',
    overtime_hours INT DEFAULT 12 COMMENT '주당 연장근로시간',
    overtime_limit INT DEFAULT 52 COMMENT '연장근로한도',
    work_days JSON COMMENT '["mon","tue","wed","thu","fri"]',
    start_time TIME DEFAULT '09:00:00',
    end_time TIME DEFAULT '18:00:00',
    break_minutes INT DEFAULT 60,
    break_start TIME DEFAULT '12:00:00',
    break_end TIME DEFAULT '13:00:00',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```

### 테이블: `attendance_settings`
```sql
CREATE TABLE attendance_settings (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL UNIQUE,
    use_gps BOOLEAN DEFAULT FALSE,
    allowed_radius INT DEFAULT 100 COMMENT '허용반경(m)',
    hq_address VARCHAR(255),
    hq_latitude DECIMAL(10,8),
    hq_longitude DECIMAL(11,8),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```

### 테이블: `sites` (현장)
```sql
CREATE TABLE sites (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    name VARCHAR(100) NOT NULL,
    address VARCHAR(255),
    latitude DECIMAL(10,8),
    longitude DECIMAL(11,8),
    is_active BOOLEAN DEFAULT TRUE,
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_tenant (tenant_id)
);
```

### API 엔드포인트
```
GET    /v1/settings/work            # 근무 설정 조회
PUT    /v1/settings/work            # 근무 설정 수정

GET    /v1/settings/attendance      # 출퇴근 설정 조회
PUT    /v1/settings/attendance      # 출퇴근 설정 수정

GET    /v1/sites                    # 현장 목록
POST   /v1/sites                    # 현장 등록
GET    /v1/sites/{id}               # 현장 상세
PUT    /v1/sites/{id}               # 현장 수정
DELETE /v1/sites/{id}               # 현장 삭제
```

---

## 2.3 카드/계좌 관리

### 스펙
- 스토리보드: 슬라이드 101-104
- 보안: 카드번호/비밀번호 암호화 필수

### 테이블: `cards`
```sql
CREATE TABLE cards (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    card_company VARCHAR(50) NOT NULL COMMENT '카드사',
    card_number_encrypted TEXT NOT NULL COMMENT '암호화된 카드번호',
    card_number_last4 VARCHAR(4) NOT NULL COMMENT '끝 4자리',
    expiry_date VARCHAR(5) NOT NULL COMMENT 'MM/YY',
    card_password_encrypted TEXT COMMENT '암호화된 비밀번호 앞2자리',
    card_name VARCHAR(100) NOT NULL,
    status VARCHAR(20) DEFAULT 'active' COMMENT 'active/inactive',
    assigned_user_id BIGINT NULL,
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_tenant (tenant_id),
    INDEX idx_status (status)
);
```

### 테이블: `bank_accounts`
```sql
CREATE TABLE bank_accounts (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    bank_code VARCHAR(10) NOT NULL,
    bank_name VARCHAR(50) NOT NULL,
    account_number VARCHAR(30) NOT NULL,
    account_holder VARCHAR(50) NOT NULL,
    account_name VARCHAR(100) NOT NULL COMMENT '계좌별칭',
    status VARCHAR(20) DEFAULT 'active' COMMENT 'active/inactive',
    assigned_user_id BIGINT NULL,
    is_primary BOOLEAN DEFAULT FALSE COMMENT '대표계좌',
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_tenant (tenant_id),
    INDEX idx_status (status)
);
```

### API 엔드포인트
```
GET    /v1/cards                    # 카드 목록 (필터: status)
POST   /v1/cards                    # 카드 등록
GET    /v1/cards/{id}               # 카드 상세
PUT    /v1/cards/{id}               # 카드 수정
DELETE /v1/cards/{id}               # 카드 삭제
PATCH  /v1/cards/{id}/toggle        # 사용/정지 토글

GET    /v1/bank-accounts            # 계좌 목록 (필터: status)
POST   /v1/bank-accounts            # 계좌 등록
GET    /v1/bank-accounts/{id}       # 계좌 상세
PUT    /v1/bank-accounts/{id}       # 계좌 수정
DELETE /v1/bank-accounts/{id}       # 계좌 삭제
PATCH  /v1/bank-accounts/{id}/toggle      # 사용/정지 토글
PATCH  /v1/bank-accounts/{id}/set-primary # 대표계좌 설정
```

---

## 2.4 입금/출금 관리

### 스펙
- 스토리보드: 슬라이드 68-77
- 의존성: `clients`, `bank_accounts` 참조

### 테이블: `deposits` (입금)
```sql
CREATE TABLE deposits (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    deposit_date DATE NOT NULL,
    client_id BIGINT NULL,
    client_name VARCHAR(100) COMMENT '비회원 거래처명',
    bank_account_id BIGINT NULL,
    amount DECIMAL(15,2) NOT NULL,
    payment_method VARCHAR(20) NOT NULL COMMENT 'cash/transfer/card/check',
    account_code VARCHAR(20) COMMENT '계정과목',
    description TEXT,
    reference_type VARCHAR(50) NULL COMMENT 'sales/receivable/etc',
    reference_id BIGINT NULL,
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_tenant_date (tenant_id, deposit_date),
    INDEX idx_client (client_id)
);
```

### 테이블: `withdrawals` (출금)
```sql
CREATE TABLE withdrawals (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    withdrawal_date DATE NOT NULL,
    client_id BIGINT NULL,
    client_name VARCHAR(100),
    bank_account_id BIGINT NULL,
    amount DECIMAL(15,2) NOT NULL,
    payment_method VARCHAR(20) NOT NULL COMMENT 'cash/transfer/card/check',
    account_code VARCHAR(20) COMMENT '계정과목',
    description TEXT,
    reference_type VARCHAR(50) NULL COMMENT 'purchase/payable/payroll/etc',
    reference_id BIGINT NULL,
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_tenant_date (tenant_id, withdrawal_date),
    INDEX idx_client (client_id)
);
```

### API 엔드포인트
```
GET    /v1/deposits                 # 입금 목록 (필터: date_range, client_id, payment_method)
POST   /v1/deposits                 # 입금 등록
GET    /v1/deposits/{id}            # 입금 상세
PUT    /v1/deposits/{id}            # 입금 수정
DELETE /v1/deposits/{id}            # 입금 삭제
GET    /v1/deposits/summary         # 입금 요약 (기간별 합계)

GET    /v1/withdrawals              # 출금 목록
POST   /v1/withdrawals              # 출금 등록
GET    /v1/withdrawals/{id}         # 출금 상세
PUT    /v1/withdrawals/{id}         # 출금 수정
DELETE /v1/withdrawals/{id}         # 출금 삭제
GET    /v1/withdrawals/summary      # 출금 요약
```

---

## 2.5 매출/매입 관리

### 스펙
- 스토리보드: 슬라이드 78-87
- 의존성: `clients`, `deposits`, `withdrawals` 참조

### 테이블: `sales` (매출)
```sql
CREATE TABLE sales (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    sale_number VARCHAR(30) NOT NULL COMMENT '매출번호',
    sale_date DATE NOT NULL,
    client_id BIGINT NOT NULL,
    supply_amount DECIMAL(15,2) NOT NULL COMMENT '공급가액',
    tax_amount DECIMAL(15,2) NOT NULL COMMENT '세액',
    total_amount DECIMAL(15,2) NOT NULL COMMENT '합계',
    description TEXT,
    status VARCHAR(20) DEFAULT 'draft' COMMENT 'draft/confirmed/invoiced',
    tax_invoice_id BIGINT NULL COMMENT '세금계산서 ID',
    deposit_id BIGINT NULL COMMENT '입금 연결',
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_tenant_number (tenant_id, sale_number),
    INDEX idx_tenant_date (tenant_id, sale_date),
    INDEX idx_client (client_id)
);
```

### 테이블: `purchases` (매입)
```sql
CREATE TABLE purchases (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    purchase_number VARCHAR(30) NOT NULL,
    purchase_date DATE NOT NULL,
    client_id BIGINT NOT NULL,
    supply_amount DECIMAL(15,2) NOT NULL,
    tax_amount DECIMAL(15,2) NOT NULL,
    total_amount DECIMAL(15,2) NOT NULL,
    description TEXT,
    status VARCHAR(20) DEFAULT 'draft' COMMENT 'draft/confirmed',
    withdrawal_id BIGINT NULL COMMENT '출금 연결',
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_tenant_number (tenant_id, purchase_number),
    INDEX idx_tenant_date (tenant_id, purchase_date),
    INDEX idx_client (client_id)
);
```

### API 엔드포인트
```
GET    /v1/sales                    # 매출 목록
POST   /v1/sales                    # 매출 등록
GET    /v1/sales/{id}               # 매출 상세
PUT    /v1/sales/{id}               # 매출 수정
DELETE /v1/sales/{id}               # 매출 삭제
POST   /v1/sales/{id}/confirm       # 매출 확정
POST   /v1/sales/{id}/tax-invoice   # 세금계산서 발행 (바로빌 연동)

GET    /v1/purchases                # 매입 목록
POST   /v1/purchases                # 매입 등록
GET    /v1/purchases/{id}           # 매입 상세
PUT    /v1/purchases/{id}           # 매입 수정
DELETE /v1/purchases/{id}           # 매입 삭제
POST   /v1/purchases/{id}/confirm   # 매입 확정
```

---

## 2.6 보고서

### 스펙
- 스토리보드: 슬라이드 106-109
- 의존성: `deposits`, `withdrawals`, `sales`, `purchases` 집계

### API 엔드포인트
```
GET    /v1/reports/daily                    # 일일 일보
       Query: date (기준일)
       Response: {
           previous_balance, daily_deposit, daily_withdrawal, current_balance,
           details: [{ type, client_name, account_code, deposit_amount, withdrawal_amount, description }]
       }

GET    /v1/reports/daily/export             # 일일 일보 엑셀 다운로드
       Query: date
       Response: Excel file

GET    /v1/reports/expense-estimate         # 지출 예상 내역서
       Query: year_month
       Response: {
           total_estimate, account_balance, expected_balance,
           items: [{ expected_date, item_name, amount, client_name, account_name }],
           monthly_summary: [{ month, total }]
       }

GET    /v1/reports/expense-estimate/export  # 엑셀 다운로드
       Query: year_month
       Response: Excel file
```

---

# Part 3: 신규 개발

## 3.1 전자결재 모듈

### 스펙
- 스토리보드: 슬라이드 47-59
- 핵심 기능: 기안/결재/반려/참조

### 테이블: `approval_forms` (결재 양식)
```sql
CREATE TABLE approval_forms (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    name VARCHAR(100) NOT NULL COMMENT '양식명',
    code VARCHAR(50) NOT NULL COMMENT '양식코드',
    category VARCHAR(50) COMMENT '분류',
    template JSON NOT NULL COMMENT '양식 템플릿 (필드 정의)',
    is_active BOOLEAN DEFAULT TRUE,
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_tenant_code (tenant_id, code),
    INDEX idx_tenant (tenant_id)
);
```

### 테이블: `approval_lines` (결재선 템플릿)
```sql
CREATE TABLE approval_lines (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    name VARCHAR(100) NOT NULL,
    steps JSON NOT NULL COMMENT '[{order, type: approval/agreement/reference, user_id, position}]',
    is_default BOOLEAN DEFAULT FALSE,
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_tenant (tenant_id)
);
```

### 테이블: `approvals` (결재 문서)
```sql
CREATE TABLE approvals (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    document_number VARCHAR(50) NOT NULL,
    form_id BIGINT NOT NULL,
    title VARCHAR(200) NOT NULL,
    content JSON NOT NULL COMMENT '양식 데이터',
    status VARCHAR(20) DEFAULT 'draft' COMMENT 'draft/pending/approved/rejected/cancelled',
    drafter_id BIGINT NOT NULL COMMENT '기안자',
    drafted_at TIMESTAMP NULL,
    completed_at TIMESTAMP NULL,
    current_step INT DEFAULT 0,
    attachments JSON COMMENT '첨부파일 IDs',
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_tenant_number (tenant_id, document_number),
    INDEX idx_tenant_status (tenant_id, status),
    INDEX idx_drafter (drafter_id)
);
```

### 테이블: `approval_steps` (결재 단계)
```sql
CREATE TABLE approval_steps (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    approval_id BIGINT NOT NULL,
    step_order INT NOT NULL,
    step_type VARCHAR(20) NOT NULL COMMENT 'approval/agreement/reference',
    approver_id BIGINT NOT NULL,
    status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/approved/rejected/skipped',
    comment TEXT,
    acted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_approval (approval_id),
    INDEX idx_approver_status (approver_id, status)
);
```

### API 엔드포인트
```
# 결재 문서
GET    /v1/approvals/drafts         # 기안함 (내가 기안한 문서)
GET    /v1/approvals/inbox          # 결재함 (내 결재 대기)
GET    /v1/approvals/completed      # 결재 완료함
GET    /v1/approvals/reference      # 참조함
POST   /v1/approvals                # 문서 기안
GET    /v1/approvals/{id}           # 문서 상세
PUT    /v1/approvals/{id}           # 문서 수정 (draft만)
DELETE /v1/approvals/{id}           # 문서 삭제/회수
POST   /v1/approvals/{id}/submit    # 결재 상신
POST   /v1/approvals/{id}/approve   # 승인 (comment 선택)
POST   /v1/approvals/{id}/reject    # 반려 (comment 필수)

# 결재선 템플릿
GET    /v1/approval-lines           # 결재선 목록
POST   /v1/approval-lines           # 결재선 생성
PUT    /v1/approval-lines/{id}      # 결재선 수정
DELETE /v1/approval-lines/{id}      # 결재선 삭제

# 결재 양식
GET    /v1/approval-forms           # 양식 목록
POST   /v1/approval-forms           # 양식 생성
GET    /v1/approval-forms/{id}      # 양식 상세
PUT    /v1/approval-forms/{id}      # 양식 수정
DELETE /v1/approval-forms/{id}      # 양식 삭제
```

### 상태 전이
```
draft → pending (상신) → approved (전체 승인)
                      → rejected (반려)
pending → cancelled (회수, 기안자만)
rejected → draft (재기안 시 새 문서 생성)
```

---

## 3.2 급여 관리

### 스펙
- 스토리보드: 미상세 (회계관리 연계)
- 의존성: `employees`, `attendances`, `leaves`

### 테이블: `payrolls`
```sql
CREATE TABLE payrolls (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    pay_year INT NOT NULL,
    pay_month INT NOT NULL,
    base_salary DECIMAL(15,2) NOT NULL COMMENT '기본급',
    overtime_pay DECIMAL(15,2) DEFAULT 0 COMMENT '연장근로수당',
    bonus DECIMAL(15,2) DEFAULT 0 COMMENT '상여금',
    allowances JSON COMMENT '수당 상세 [{name, amount}]',
    gross_salary DECIMAL(15,2) NOT NULL COMMENT '총지급액',
    income_tax DECIMAL(15,2) DEFAULT 0 COMMENT '소득세',
    resident_tax DECIMAL(15,2) DEFAULT 0 COMMENT '주민세',
    health_insurance DECIMAL(15,2) DEFAULT 0 COMMENT '건강보험',
    pension DECIMAL(15,2) DEFAULT 0 COMMENT '국민연금',
    employment_insurance DECIMAL(15,2) DEFAULT 0 COMMENT '고용보험',
    deductions JSON COMMENT '공제 상세',
    total_deductions DECIMAL(15,2) NOT NULL COMMENT '총공제액',
    net_salary DECIMAL(15,2) NOT NULL COMMENT '실수령액',
    status VARCHAR(20) DEFAULT 'draft' COMMENT 'draft/confirmed/paid',
    paid_at TIMESTAMP NULL,
    withdrawal_id BIGINT NULL COMMENT '출금 연결',
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_tenant_user_month (tenant_id, user_id, pay_year, pay_month),
    INDEX idx_tenant_month (tenant_id, pay_year, pay_month)
);
```

### API 엔드포인트
```
GET    /v1/payrolls                 # 급여 목록 (필터: year, month, user_id, status)
POST   /v1/payrolls                 # 급여 등록
GET    /v1/payrolls/{id}            # 급여 상세
PUT    /v1/payrolls/{id}            # 급여 수정 (draft만)
DELETE /v1/payrolls/{id}            # 급여 삭제 (draft만)
POST   /v1/payrolls/{id}/confirm    # 급여 확정
POST   /v1/payrolls/{id}/pay        # 지급 처리 (출금 연결)
GET    /v1/payrolls/{id}/payslip    # 급여명세서 조회
GET    /v1/payrolls/{id}/payslip/pdf # 급여명세서 PDF

POST   /v1/payrolls/calculate       # 급여 일괄 계산
       Body: { year, month, user_ids?: [] } // user_ids 없으면 전체

GET    /v1/settings/payroll         # 급여 설정 (세율, 보험료율 등)
PUT    /v1/settings/payroll         # 급여 설정 수정
```

---

## 3.3 대시보드

### 스펙
- 스토리보드: 미상세 (진입점 화면)
- 의존성: 전 모듈 집계

### API 엔드포인트
```
GET    /v1/dashboard/summary
       Response: {
           today: { date, attendances_count, leaves_count, approvals_pending },
           finance: { monthly_deposit, monthly_withdrawal, balance },
           sales: { monthly_sales, monthly_purchases },
           tasks: { pending_approvals, pending_leaves }
       }

GET    /v1/dashboard/charts
       Query: period (week/month/quarter)
       Response: {
           deposit_trend: [{ date, amount }],
           withdrawal_trend: [{ date, amount }],
           sales_by_client: [{ client_name, amount }]
       }

GET    /v1/dashboard/notifications
       Query: limit (default 10)
       Response: {
           items: [{ id, type, message, is_read, created_at }]
       }

GET    /v1/dashboard/approvals
       Response: {
           items: [{ id, title, drafter_name, status, created_at }]
       }
```

---

## 3.4 AI 리포트

### 스펙
- 스토리보드: 슬라이드 113
- 의존성: 전 모듈 데이터 분석

### 테이블: `ai_reports`
```sql
CREATE TABLE ai_reports (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    report_date DATE NOT NULL,
    report_type VARCHAR(50) NOT NULL COMMENT 'daily/weekly/monthly',
    content JSON NOT NULL COMMENT '리포트 내용',
    summary TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_tenant_date (tenant_id, report_date)
);
```

### API 엔드포인트
```
GET    /v1/reports/ai               # AI 리포트 목록
POST   /v1/reports/ai/generate      # AI 리포트 생성
       Body: { report_type: 'daily'|'weekly'|'monthly', target_date? }
GET    /v1/reports/ai/{id}          # AI 리포트 상세
DELETE /v1/reports/ai/{id}          # AI 리포트 삭제
```

### AI 리포트 출력 형식
```json
{
    "report": [
        { "area": "지출분석", "status": "warning", "message": "...", "detail": "..." },
        { "area": "미수금", "status": "caution", "message": "...", "detail": "..." }
    ],
    "summary": "전체 요약 메시지"
}
```
- status: `warning`(빨강), `caution`(주황), `positive`(녹색), `normal`(파랑)

---

## 3.5 가지급금 관리

### 스펙
- 스토리보드: 슬라이드 110-112
- 인정이자율: 4.6% (연도별 변동)

### 테이블: `loans` (가지급금)
```sql
CREATE TABLE loans (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL COMMENT '가지급금 수령자',
    loan_date DATE NOT NULL COMMENT '지급일',
    amount DECIMAL(15,2) NOT NULL COMMENT '가지급금액',
    purpose TEXT COMMENT '사용목적',
    settlement_date DATE NULL COMMENT '정산일',
    settlement_amount DECIMAL(15,2) NULL COMMENT '정산금액',
    status VARCHAR(20) DEFAULT 'outstanding' COMMENT 'outstanding/settled/partial',
    withdrawal_id BIGINT NULL COMMENT '출금 연결',
    created_by BIGINT,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_tenant_user (tenant_id, user_id),
    INDEX idx_status (status)
);
```

### API 엔드포인트
```
GET    /v1/loans                    # 가지급금 목록
POST   /v1/loans                    # 가지급금 등록
GET    /v1/loans/{id}               # 가지급금 상세
PUT    /v1/loans/{id}               # 가지급금 수정
DELETE /v1/loans/{id}               # 가지급금 삭제
POST   /v1/loans/{id}/settle        # 정산 처리
       Body: { settlement_date, settlement_amount }

POST   /v1/loans/calculate-interest # 인정이자 계산
       Body: { year, user_id? }
       Response: {
           balance, interest_rate, recognized_interest,
           corporate_tax, income_tax, local_tax, total_tax
       }

GET    /v1/reports/loan-interest    # 인정이자 리포트
       Query: year
```

### 계산 공식
```
경과일수 = 정산일 - 지급일
일이자율 = 연이자율 / 365
인정이자 = 가지급금 × 일이자율 × 경과일수
법인세추가 = 인정이자 × 0.19
소득세추가 = 인정이자 × 0.35
지방소득세 = 소득세추가 × 0.10
```

---

## 3.6 구독/결제 관리

### 스펙
- 스토리보드: 미상세 (SaaS 과금)
- 별도 결제 시스템 연동 필요

### 테이블: `subscriptions`
```sql
CREATE TABLE subscriptions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL UNIQUE,
    plan_code VARCHAR(50) NOT NULL COMMENT 'basic/standard/premium',
    status VARCHAR(20) DEFAULT 'active' COMMENT 'trial/active/expired/cancelled',
    started_at DATE NOT NULL,
    expires_at DATE NOT NULL,
    user_limit INT DEFAULT 5,
    storage_limit_mb INT DEFAULT 1024,
    auto_renew BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```

### 테이블: `payments`
```sql
CREATE TABLE payments (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    subscription_id BIGINT NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    payment_method VARCHAR(20) COMMENT 'card/transfer',
    status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/completed/failed/refunded',
    paid_at TIMESTAMP NULL,
    pg_transaction_id VARCHAR(100),
    receipt_url VARCHAR(500),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_tenant (tenant_id),
    INDEX idx_status (status)
);
```

### API 엔드포인트
```
GET    /v1/subscriptions            # 구독 정보
POST   /v1/subscriptions            # 구독 신청/변경
PUT    /v1/subscriptions/{id}       # 구독 수정
DELETE /v1/subscriptions/{id}       # 구독 해지

GET    /v1/payments                 # 결제 내역
GET    /v1/payments/{id}            # 결제 상세
POST   /v1/payments/retry/{id}      # 결제 재시도
```

---

## 3.7 고객센터

### 스펙
- 스토리보드: 미상세 (CS 기능)

### 테이블: `support_tickets`
```sql
CREATE TABLE support_tickets (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NULL COMMENT 'NULL이면 비회원 문의',
    user_id BIGINT NULL,
    ticket_number VARCHAR(20) NOT NULL,
    category VARCHAR(50) NOT NULL COMMENT '문의유형',
    title VARCHAR(200) NOT NULL,
    content TEXT NOT NULL,
    status VARCHAR(20) DEFAULT 'open' COMMENT 'open/in_progress/resolved/closed',
    priority VARCHAR(20) DEFAULT 'normal' COMMENT 'low/normal/high/urgent',
    assigned_to BIGINT NULL COMMENT '담당자',
    resolved_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_ticket_number (ticket_number),
    INDEX idx_status (status)
);
```

### 테이블: `support_ticket_replies`
```sql
CREATE TABLE support_ticket_replies (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    ticket_id BIGINT NOT NULL,
    user_id BIGINT NULL COMMENT 'NULL이면 관리자 답변',
    is_staff BOOLEAN DEFAULT FALSE,
    content TEXT NOT NULL,
    attachments JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_ticket (ticket_id)
);
```

### 테이블: `faqs`
```sql
CREATE TABLE faqs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    category VARCHAR(50) NOT NULL,
    question VARCHAR(500) NOT NULL,
    answer TEXT NOT NULL,
    sort_order INT DEFAULT 0,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_category (category)
);
```

### API 엔드포인트
```
GET    /v1/support/tickets          # 문의 목록
POST   /v1/support/tickets          # 문의 등록
GET    /v1/support/tickets/{id}     # 문의 상세
PUT    /v1/support/tickets/{id}     # 문의 수정
POST   /v1/support/tickets/{id}/reply  # 답변/추가문의

GET    /v1/support/faq              # FAQ 목록 (필터: category)
GET    /v1/support/faq/{id}         # FAQ 상세
```

---

## 3.8 바로빌 연동

### 스펙
- 스토리보드: 슬라이드 63, 78
- 외부 API: 바로빌 (사업자조회, 세금계산서)

### 테이블: `tax_invoices`
```sql
CREATE TABLE tax_invoices (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_id BIGINT NOT NULL,
    invoice_type VARCHAR(20) NOT NULL COMMENT 'sales/purchase',
    invoice_number VARCHAR(50) NOT NULL COMMENT '승인번호',
    issue_date DATE NOT NULL,
    supplier_business_no VARCHAR(20) NOT NULL,
    supplier_name VARCHAR(100) NOT NULL,
    buyer_business_no VARCHAR(20) NOT NULL,
    buyer_name VARCHAR(100) NOT NULL,
    supply_amount DECIMAL(15,2) NOT NULL,
    tax_amount DECIMAL(15,2) NOT NULL,
    total_amount DECIMAL(15,2) NOT NULL,
    status VARCHAR(20) DEFAULT 'issued' COMMENT 'issued/cancelled',
    barobill_id VARCHAR(100) COMMENT '바로빌 문서 ID',
    reference_type VARCHAR(50) NULL COMMENT 'sales/purchases',
    reference_id BIGINT NULL,
    issued_at TIMESTAMP NULL,
    cancelled_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_tenant_date (tenant_id, issue_date),
    INDEX idx_invoice_number (invoice_number)
);
```

### API 엔드포인트
```
POST   /v1/external/barobill/verify-business
       Body: { business_number }
       Response: { valid, company_name, ceo_name, business_type, ... }

POST   /v1/external/barobill/tax-invoice
       Body: { invoice_type, supplier, buyer, items, ... }
       Response: { id, invoice_number, barobill_id }

GET    /v1/external/barobill/tax-invoice/{id}
       Response: 세금계산서 상세

POST   /v1/external/barobill/tax-invoice/{id}/cancel
       Response: 취소 결과

GET    /v1/tax-invoices             # 세금계산서 목록 (내부 조회용)
GET    /v1/tax-invoices/{id}        # 세금계산서 상세
```

---

# Part 4: 기획 확인 필요

> 아래 항목은 API 구현 전 비즈니스 로직 확정 필요

## 4.1 상태 전이 조건

| 대상 | 확인 항목 | 기본값 (확정 전) |
|------|----------|------------------|
| 테넌트 | 신청→승인→만료→해지 전이 조건 | 수동 전환 |
| 사원 | 휴직→복직/퇴사 전이 조건 | 수동 전환 |
| 결재 | 반려 후 재기안 프로세스 | 새 문서 생성 |
| 미수금 | 연체 판정 기준일 | 30일 |
| 악성채권 | 악성채권 판정 조건 | 90일 + 수동 |

## 4.2 모듈 간 연동

| 연동 | 확인 항목 | 기본값 |
|------|----------|--------|
| 전자결재→회계 | 지출결의서 승인 시 출금 자동 생성 | 수동 연결 |
| 휴가신청→결재 | 휴가가 결재 문서로 생성되는지 | 별도 관리 |
| 휴가승인→근태 | 승인 휴가가 근태 자동 반영 | 자동 반영 |
| GPS출퇴근→근태 | GPS 기록이 근태 자동 반영 | 자동 반영 |
| 급여→출금 | 급여 확정 시 출금 자동 생성 | 수동 연결 |

## 4.3 외부 연동

| 항목 | 확인 필요 |
|------|----------|
| 바로빌 API 비용 | 호출당 비용, 월 한도 |
| 연동 은행 범위 | 지원 은행 목록 |
| 연동 실패 처리 | Retry 정책, fallback |

## 4.4 MES 메뉴

| 메뉴 | 확인 필요 |
|------|----------|
| 영업관리 | 기존 Quote API 활용 여부 |
| 판매관리 | 상세 요구사항 |
| 구매관리 | 상세 요구사항 |

## 4.5 어음관리

| 항목 | 확인 필요 |
|------|----------|
| 기능 필요성 | 사용 여부 |
| 상세 기능 | 등록/만기일/추심/부도 |

---

# 개발 순서

```
Phase 1: 확장 개발 (1-2주)
├── 2.1 휴가 관리
├── 2.2 근무/출퇴근 설정
├── 2.3 카드/계좌 관리
├── 2.4 입금/출금 관리
├── 2.5 매출/매입 관리
└── 2.6 보고서

Phase 2: 핵심 신규 (2-4주)
├── 3.1 전자결재 모듈 ★
├── 3.2 급여 관리
└── 3.3 대시보드

Phase 3: 추가 기능 (4-6주)
├── 3.4 AI 리포트
├── 3.5 가지급금 관리
└── 3.8 바로빌 연동

Phase 4: SaaS 기능 (별도)
├── 3.6 구독/결제 관리
└── 3.7 고객센터
```

---

# 참고 문서

| 문서 | 경로 |
|------|------|
| DB 스키마 | `docs/specs/database-schema.md` |
| 시스템 아키텍처 | `docs/architecture/system-overview.md` |
| HR API 분석 | `docs/features/hr/hr-api-analysis.md` |
| 기존 API 라우트 | `api/routes/api.php` |
| 스토리보드 상세 | `docs/specs/erp-analysis/01-common.md` ~ `08-reports.md` |