# 거래처 관리 정책 (Client Management Policy)

> **작성일**: 2025-12-08
> **상태**: 설계 확정
> **관련 요청**: `docs/front/[API-2025-12-04] client-api-analysis.md`

---

## 1. 개요

### 1.1 목적
- **거래처 마스터** 통합 관리 (매입처/매출처/매입매출)
- **고객그룹** 기반 단가 정책 연계
- 세금계산서 합의 및 **불량 채권** 관리
- 주문/발주 연동을 위한 거래처 기반 시스템

### 1.2 핵심 원칙

| 원칙 | 설명 |
|------|------|
| **거래처 유형 분류** | 매입, 매출, 매입매출로 구분 관리 |
| **코드 유일성** | tenant 내 client_code 중복 불가 |
| **그룹 기반 단가** | 고객그룹별 차등 판매가 적용 |
| **연관 데이터 보호** | 주문 존재 시 삭제 불가 |

---

## 2. 테이블 구조

### 2.1 ERD 개요

```
┌─────────────────┐       ┌─────────────────────┐
│  client_groups  │──────<│      clients        │
│   (고객 그룹)    │       │     (거래처)        │
└────────┬────────┘       └─────────┬───────────┘
         │                          │
         │ price_rate               │ client_id
         │                          │
         ▼                          ▼
┌─────────────────┐       ┌─────────────────────┐
│     prices      │       │      orders         │
│    (차등 단가)   │       │      (주문)         │
└─────────────────┘       └─────────────────────┘
```

### 2.2 clients 테이블 (거래처 마스터)

```sql
CREATE TABLE clients (
    id                      BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id               BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',
    client_group_id         BIGINT UNSIGNED NULL COMMENT '고객그룹 ID',

    -- 기본 정보 --
    client_code             VARCHAR(50) NOT NULL COMMENT '거래처 코드',
    name                    VARCHAR(100) NOT NULL COMMENT '거래처명',
    client_type             VARCHAR(20) NULL COMMENT '거래처 유형 (매입/매출/매입매출)',
    is_active               TINYINT(1) DEFAULT 1 COMMENT '활성여부(1=활성,0=비활성)',

    -- 사업자 정보 --
    business_no             VARCHAR(20) NULL COMMENT '사업자등록번호',
    business_type           VARCHAR(50) NULL COMMENT '업태',
    business_item           VARCHAR(100) NULL COMMENT '업종',

    -- 연락처 정보 --
    contact_person          VARCHAR(50) NULL COMMENT '담당자명',
    phone                   VARCHAR(30) NULL COMMENT '전화번호',
    mobile                  VARCHAR(20) NULL COMMENT '휴대폰',
    fax                     VARCHAR(20) NULL COMMENT '팩스',
    email                   VARCHAR(80) NULL COMMENT '이메일',
    address                 VARCHAR(255) NULL COMMENT '주소',

    -- 담당자 정보 --
    manager_name            VARCHAR(50) NULL COMMENT '당사 담당자명',
    manager_tel             VARCHAR(20) NULL COMMENT '당사 담당자 연락처',
    system_manager          VARCHAR(50) NULL COMMENT '시스템 담당자',

    -- 계정 정보 --
    account_id              VARCHAR(50) NULL COMMENT '계정 ID',
    account_password        VARCHAR(255) NULL COMMENT '계정 비밀번호 (hidden)',

    -- 발주/결제 설정 --
    purchase_payment_day    VARCHAR(20) NULL COMMENT '매입 결제일',
    sales_payment_day       VARCHAR(20) NULL COMMENT '매출 결제일',

    -- 세금계산서 합의 --
    tax_agreement           BOOLEAN DEFAULT FALSE COMMENT '세금계산서 합의 여부',
    tax_amount              DECIMAL(15,2) NULL COMMENT '합의 금액',
    tax_start_date          DATE NULL COMMENT '합의 시작일',
    tax_end_date            DATE NULL COMMENT '합의 종료일',

    -- 불량 채권 관리 --
    bad_debt                BOOLEAN DEFAULT FALSE COMMENT '불량 채권 여부',
    bad_debt_amount         DECIMAL(15,2) NULL COMMENT '불량 채권 금액',
    bad_debt_receive_date   DATE NULL COMMENT '채권 발생일',
    bad_debt_end_date       DATE NULL COMMENT '채권 종료일',
    bad_debt_progress       VARCHAR(20) NULL COMMENT '진행 상태',

    -- 기타 --
    memo                    TEXT NULL COMMENT '메모',

    -- 감사 컬럼 --
    created_by              BIGINT UNSIGNED NULL COMMENT '생성자 ID',
    updated_by              BIGINT UNSIGNED NULL COMMENT '수정자 ID',
    created_at              TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at              TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    -- 인덱스 --
    INDEX idx_clients_tenant (tenant_id),
    INDEX idx_clients_code (tenant_id, client_code),
    INDEX idx_clients_group (tenant_id, client_group_id),
    INDEX idx_clients_type (tenant_id, client_type),
    INDEX idx_clients_active (tenant_id, is_active),
    UNIQUE idx_clients_unique (tenant_id, client_code),

    FOREIGN KEY (client_group_id) REFERENCES client_groups(id) ON DELETE SET NULL
) COMMENT='거래처 마스터';
```

### 2.3 client_groups 테이블 (고객 그룹)

```sql
CREATE TABLE client_groups (
    id                  BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tenant_id           BIGINT UNSIGNED NOT NULL COMMENT '테넌트 ID',

    -- 그룹 정보 --
    group_code          VARCHAR(50) NOT NULL COMMENT '그룹 코드',
    group_name          VARCHAR(100) NOT NULL COMMENT '그룹명',
    price_rate          DECIMAL(5,4) DEFAULT 1.0000 COMMENT '단가 비율 (1.0 = 100%)',
    is_active           BOOLEAN DEFAULT TRUE COMMENT '활성 상태',

    -- 감사 컬럼 --
    created_by          BIGINT UNSIGNED NULL COMMENT '생성자 ID',
    updated_by          BIGINT UNSIGNED NULL COMMENT '수정자 ID',
    deleted_by          BIGINT UNSIGNED NULL COMMENT '삭제자 ID',
    created_at          TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at          TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at          TIMESTAMP NULL COMMENT 'Soft Delete',

    -- 인덱스 --
    INDEX idx_groups_tenant (tenant_id),
    INDEX idx_groups_code (tenant_id, group_code),
    INDEX idx_groups_active (tenant_id, is_active),
    UNIQUE idx_groups_unique (tenant_id, group_code, deleted_at)
) COMMENT='고객 그룹';
```

---

## 3. 거래처 유형 정책

### 3.1 거래처 유형 (client_type)

| 유형 | 설명 | 용도 |
|------|------|------|
| **매입** | 자재/상품 구매처 | 발주, 입고, 매입 관리 |
| **매출** | 제품/상품 판매처 | 주문, 출고, 매출 관리 |
| **매입매출** | 양방향 거래처 | 매입+매출 모두 가능 |

### 3.2 유형별 업무 흐름

```
┌───────────────────────────────────────────────────────────────┐
│                        매입 거래처                              │
├───────────────────────────────────────────────────────────────┤
│  발주요청 → 발주서 → 입고 → 수입검사 → 매입확정 → 대금지급    │
└───────────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────────┐
│                        매출 거래처                              │
├───────────────────────────────────────────────────────────────┤
│  견적 → 수주 → 생산 → 출고 → 매출확정 → 대금수금              │
└───────────────────────────────────────────────────────────────┘
```

---

## 4. 고객그룹과 단가 연계

### 4.1 단가 적용 우선순위

```
┌─────────────────────────────────────────────────────────┐
│                  판매가 조회 로직                        │
├─────────────────────────────────────────────────────────┤
│  1순위: 고객그룹별 특별 단가                            │
│         prices WHERE client_group_id = [거래처.그룹ID]  │
│                                                         │
│  2순위: 기본 판매가                                     │
│         prices WHERE client_group_id IS NULL            │
└─────────────────────────────────────────────────────────┘
```

### 4.2 단가 비율 적용 (price_rate)

```
최종 판매가 = 기본 판매가 × price_rate

예시:
- 기본 판매가: 10,000원
- VIP 그룹 price_rate: 0.90 (10% 할인)
- 최종 판매가: 10,000 × 0.90 = 9,000원
```

### 4.3 그룹 활용 예시

| 그룹 코드 | 그룹명 | price_rate | 설명 |
|----------|--------|------------|------|
| `DEFAULT` | 일반 고객 | 1.0000 | 정가 적용 |
| `VIP` | VIP 고객 | 0.9000 | 10% 할인 |
| `WHOLESALE` | 도매 거래처 | 0.8000 | 20% 할인 |
| `PARTNER` | 협력 업체 | 0.8500 | 15% 할인 |

---

## 5. 세금계산서 합의 관리

### 5.1 합의 정보 구조

| 필드 | 설명 | 용도 |
|------|------|------|
| `tax_agreement` | 합의 여부 | 세금계산서 발행 방식 결정 |
| `tax_amount` | 합의 금액 | 월정액 또는 고정 금액 |
| `tax_start_date` | 시작일 | 합의 유효 기간 시작 |
| `tax_end_date` | 종료일 | 합의 유효 기간 종료 |

### 5.2 합의 유형

```
┌─────────────────────────────────────────────────────────┐
│   tax_agreement = FALSE                                 │
│   → 실제 거래 금액으로 세금계산서 발행                   │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│   tax_agreement = TRUE                                  │
│   → 합의 금액(tax_amount)으로 세금계산서 발행           │
│   → 유효 기간: tax_start_date ~ tax_end_date            │
└─────────────────────────────────────────────────────────┘
```

---

## 6. 불량 채권 관리

### 6.1 불량 채권 정보 구조

| 필드 | 설명 |
|------|------|
| `bad_debt` | 불량 채권 여부 |
| `bad_debt_amount` | 불량 채권 금액 |
| `bad_debt_receive_date` | 채권 발생일 |
| `bad_debt_end_date` | 채권 종료(해결)일 |
| `bad_debt_progress` | 진행 상태 |

### 6.2 진행 상태 (bad_debt_progress)

```
┌──────────┐     협의     ┌──────────┐     법적조치   ┌──────────┐
│  협의중  │ ──────────> │  소송중  │ ─────────── │ 회수완료 │
└──────────┘              └────┬─────┘              └──────────┘
                               │
                               │ 회수 불가
                               ▼
                          ┌──────────┐
                          │ 대손처리 │
                          └──────────┘
```

### 6.3 불량 채권 상태값

| 상태 | 설명 | 후속 조치 |
|------|------|----------|
| `협의중` | 채권 회수 협의 진행 | 정기 연락, 분할 상환 협의 |
| `소송중` | 법적 절차 진행 | 법무팀 연계, 소송 진행 |
| `회수완료` | 채권 전액 회수 | bad_debt_end_date 기록 |
| `대손처리` | 회수 불가 판정 | 회계 처리, 거래 중단 검토 |

---

## 7. 상태 관리

### 7.1 활성 상태 (is_active)

```
┌──────────────┐     비활성화     ┌──────────────┐
│  true (활성)  │ ───────────── │  false (비활성)│
│   거래가능    │     <───────── │  거래중단      │
└──────────────┘     재활성화     └──────────────┘
```

### 7.2 상태별 제약

| 상태 | 주문 생성 | 발주 생성 | 조회 | 수정 |
|------|----------|----------|------|------|
| `true` (활성) | ✅ | ✅ | ✅ | ✅ |
| `false` (비활성) | ❌ | ❌ | ✅ | ✅ |

### 7.3 비활성화 고려 사항

- **불량 채권 거래처**: bad_debt = true 시 비활성화 검토
- **장기 미거래**: 12개월 이상 거래 없을 시 비활성화 검토
- **사업자 폐업**: 사업자등록 말소 확인 시 비활성화

---

## 8. API 엔드포인트

### 8.1 엔드포인트 목록

| Method | Endpoint | 설명 | 우선순위 |
|--------|----------|------|---------|
| GET | `/api/v1/clients` | 거래처 목록 조회 | 🔴 필수 |
| GET | `/api/v1/clients/{id}` | 거래처 상세 조회 | 🔴 필수 |
| POST | `/api/v1/clients` | 거래처 등록 | 🔴 필수 |
| PUT | `/api/v1/clients/{id}` | 거래처 수정 | 🔴 필수 |
| DELETE | `/api/v1/clients/{id}` | 거래처 삭제 | 🔴 필수 |
| POST | `/api/v1/clients/{id}/toggle` | 활성/비활성 토글 | 🟡 중요 |
| GET | `/api/v1/client-groups` | 고객그룹 목록 | 🟡 중요 |
| POST | `/api/v1/client-groups` | 고객그룹 등록 | 🟢 권장 |

### 8.2 목록 조회 API

```
GET /api/v1/clients?page=1&size=20&q=검색어&only_active=true
```

**Query Parameters:**

| 파라미터 | 타입 | 설명 |
|---------|------|------|
| `page` | int | 페이지 번호 (기본: 1) |
| `size` | int | 페이지당 개수 (기본: 20) |
| `q` | string | 검색어 (거래처명, 코드, 담당자) |
| `only_active` | bool | 활성 거래처만 조회 |

**Response:**
```json
{
  "success": true,
  "message": "message.fetched",
  "data": {
    "current_page": 1,
    "data": [
      {
        "id": 1,
        "client_code": "C001",
        "name": "ABC 상사",
        "client_type": "매입매출",
        "business_no": "123-45-67890",
        "contact_person": "홍길동",
        "is_active": true,
        "client_group": {
          "id": 1,
          "group_name": "VIP"
        }
      }
    ],
    "total": 100
  }
}
```

---

## 9. 비즈니스 규칙

### 9.1 검증 규칙

| 규칙 | 설명 |
|------|------|
| **R1** | client_code는 tenant 내 유일해야 함 |
| **R2** | client_code, name은 필수 값 |
| **R3** | client_type은 '매입', '매출', '매입매출' 중 하나 |
| **R4** | 주문이 존재하는 거래처는 삭제 불가 |
| **R5** | email 형식 검증 |
| **R6** | bad_debt_progress는 지정된 값만 허용 |
| **R7** | tax_start_date ≤ tax_end_date |

### 9.2 코드 중복 처리

```
신규 등록 시:
└─ tenant_id + client_code 조합 중복 검사
└─ 중복 시 error.duplicate_code 반환

수정 시:
└─ client_code 변경 시에만 중복 검사 수행
└─ 자기 자신 제외하고 검사
```

### 9.3 삭제 정책

| 테이블 | 삭제 방식 | 설명 |
|--------|----------|------|
| `clients` | **Hard Delete** | 연관 주문이 없는 경우만 삭제 가능 |
| `client_groups` | **Soft Delete** | `deleted_at` 컬럼 사용 |

- **연관 데이터 보호**: orders 존재 시 `error.has_orders` 반환
- **주문 있는 거래처**: 비활성화(`is_active = false`)로 처리 권장

### 9.4 비밀번호 보호

```php
protected $hidden = ['account_password'];
```

- `account_password`는 API 응답에서 자동 제외
- 저장 시 평문 저장 (필요시 암호화 적용 검토)

---

## 10. 필드 분류

### 10.1 기본 정보 (4개)

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `client_code` | string(50) | ✅ | 거래처 코드 |
| `name` | string(100) | ✅ | 거래처명 |
| `client_type` | enum | ❌ | 매입/매출/매입매출 |
| `is_active` | boolean | ❌ | 활성 상태 (기본: true) |

### 10.2 사업자 정보 (3개)

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `business_no` | string(20) | ❌ | 사업자등록번호 |
| `business_type` | string(50) | ❌ | 업태 |
| `business_item` | string(100) | ❌ | 업종 |

### 10.3 연락처 정보 (6개)

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `contact_person` | string(50) | ❌ | 담당자명 |
| `phone` | string(30) | ❌ | 전화번호 |
| `mobile` | string(20) | ❌ | 휴대폰 |
| `fax` | string(20) | ❌ | 팩스 |
| `email` | string(80) | ❌ | 이메일 |
| `address` | string(255) | ❌ | 주소 |

### 10.4 담당자 정보 (3개)

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `manager_name` | string(50) | ❌ | 당사 담당자명 |
| `manager_tel` | string(20) | ❌ | 당사 담당자 연락처 |
| `system_manager` | string(50) | ❌ | 시스템 담당자 |

### 10.5 계정/발주 정보 (4개)

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `account_id` | string(50) | ❌ | 계정 ID |
| `account_password` | string(255) | ❌ | 계정 비밀번호 (hidden) |
| `purchase_payment_day` | string(20) | ❌ | 매입 결제일 |
| `sales_payment_day` | string(20) | ❌ | 매출 결제일 |

### 10.6 세금계산서 합의 (4개)

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `tax_agreement` | boolean | ❌ | 합의 여부 |
| `tax_amount` | decimal(15,2) | ❌ | 합의 금액 |
| `tax_start_date` | date | ❌ | 합의 시작일 |
| `tax_end_date` | date | ❌ | 합의 종료일 |

### 10.7 불량 채권 (5개)

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `bad_debt` | boolean | ❌ | 불량 채권 여부 |
| `bad_debt_amount` | decimal(15,2) | ❌ | 불량 채권 금액 |
| `bad_debt_receive_date` | date | ❌ | 채권 발생일 |
| `bad_debt_end_date` | date | ❌ | 채권 종료일 |
| `bad_debt_progress` | enum | ❌ | 진행 상태 |

### 10.8 기타 (2개)

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `client_group_id` | bigint | ❌ | 고객그룹 ID |
| `memo` | text | ❌ | 메모 |

---

## 11. 관련 문서

- [단가 정책](./pricing-policy.md) - 고객그룹별 단가 적용
- [API 개발 규칙](../standards/api-rules.md)
- [프론트엔드 요청서](../front/[API-2025-12-04]%20client-api-analysis.md)

---

**최종 업데이트**: 2025-12-08
