Files
sam-docs/front/[API-2025-12-04] client-api-analysis.md
hskwon 914667738b docs: clients is_active Boolean 변경 반영
- client-policy.md: TINYINT(1), true/false 상태값 반영, Hard Delete 명시
- client-api-analysis.md: 스키마/타입 Boolean 업데이트, Soft Delete → Hard Delete 수정
2025-12-08 20:26:01 +09:00

523 lines
16 KiB
Markdown

# 거래처 관리 API 분석 및 구현 현황
> **작성일**: 2025-12-04
> **최종 업데이트**: 2025-12-08
> **상태**: ✅ **백엔드 + 프론트엔드 구현 완료**
---
## 1. 구현 현황 요약
### ✅ 백엔드 API 구조 (구현 완료)
#### Client (거래처) API
| 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}` | 삭제 | ✅ 완료 |
| `PATCH` | `/api/v1/clients/{id}/toggle` | 활성/비활성 토글 | ✅ 완료 |
#### Client Group (거래처 그룹) API
| Method | Endpoint | 설명 | 상태 |
|--------|----------|------|------|
| `GET` | `/api/v1/client-groups` | 그룹 목록 | ✅ 완료 |
| `GET` | `/api/v1/client-groups/{id}` | 그룹 단건 | ✅ 완료 |
| `POST` | `/api/v1/client-groups` | 그룹 생성 | ✅ 완료 |
| `PUT` | `/api/v1/client-groups/{id}` | 그룹 수정 | ✅ 완료 |
| `DELETE` | `/api/v1/client-groups/{id}` | 그룹 삭제 | ✅ 완료 |
| `PATCH` | `/api/v1/client-groups/{id}/toggle` | 그룹 활성/비활성 | ✅ 완료 |
### ✅ 프론트엔드 구현 현황 (구현 완료)
| 작업 | 상태 | 파일 |
|------|------|------|
| API Proxy | ✅ 완료 | `/api/proxy/[...path]/route.ts` (catch-all) |
| `useClientList` 훅 | ✅ 완료 | `src/hooks/useClientList.ts` |
| 타입 정의 (확장 필드 포함) | ✅ 완료 | `src/hooks/useClientList.ts` |
| 거래처 등록 페이지 | ✅ 완료 | `.../client-management-sales-admin/new/page.tsx` |
| 거래처 상세 페이지 | ✅ 완료 | `.../client-management-sales-admin/[id]/page.tsx` |
| 거래처 수정 페이지 | ✅ 완료 | `.../client-management-sales-admin/[id]/edit/page.tsx` |
| 목록 페이지 개선 | ✅ 완료 | 모달 삭제, 페이지 네비게이션 적용 |
---
## 2. 테이블 스키마 (구현 완료)
### 2.1 `clients` 테이블 ✅
```sql
CREATE TABLE clients (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL,
client_group_id BIGINT NULL,
-- 기본 정보
client_code VARCHAR(50) NOT NULL,
name VARCHAR(255) NOT NULL,
client_type ENUM('매입','매출','매입매출') DEFAULT '매입',
-- 사업자 정보
business_no VARCHAR(20) NULL, -- 사업자등록번호
business_type VARCHAR(50) NULL, -- 업태
business_item VARCHAR(100) NULL, -- 업종
-- 연락처 정보
contact_person VARCHAR(100) NULL, -- 대표 담당자
phone VARCHAR(20) NULL,
mobile VARCHAR(20) NULL,
fax VARCHAR(20) NULL,
email VARCHAR(255) NULL,
address TEXT NULL,
-- 담당자 정보
manager_name VARCHAR(50) NULL, -- 담당자명
manager_tel VARCHAR(20) NULL, -- 담당자 전화
system_manager VARCHAR(50) NULL, -- 시스템 관리자
-- 발주처 설정
account_id VARCHAR(50) NULL, -- 계정 ID
account_password VARCHAR(255) NULL, -- 비밀번호 (암호화, hidden)
purchase_payment_day VARCHAR(20) NULL, -- 매입 결제일
sales_payment_day VARCHAR(20) NULL, -- 매출 결제일
-- 약정 세금
tax_agreement BOOLEAN DEFAULT FALSE,
tax_amount DECIMAL(15,2) NULL,
tax_start_date DATE NULL,
tax_end_date DATE NULL,
-- 악성채권 정보
bad_debt BOOLEAN DEFAULT FALSE,
bad_debt_amount DECIMAL(15,2) NULL,
bad_debt_receive_date DATE NULL,
bad_debt_end_date DATE NULL,
bad_debt_progress ENUM('협의중','소송중','회수완료','대손처리') NULL,
-- 기타
memo TEXT NULL,
is_active TINYINT(1) DEFAULT 1, -- 1=활성, 0=비활성
-- 감사 컬럼
created_at TIMESTAMP,
updated_at TIMESTAMP
);
```
### 2.2 `client_groups` 테이블 ✅
```sql
CREATE TABLE client_groups (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL,
group_code VARCHAR(30) NOT NULL, -- 그룹 코드 (필수)
group_name VARCHAR(100) NOT NULL, -- 그룹명 (필수)
price_rate DECIMAL(6,4) NOT NULL, -- 단가율 (필수, 0~99.9999)
is_active BOOLEAN DEFAULT TRUE,
created_by BIGINT NULL,
updated_by BIGINT NULL,
deleted_by BIGINT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP,
deleted_at TIMESTAMP NULL -- Soft Delete
);
```
**⚠️ 주의**: `client_groups` API 필드명
- `group_code` (필수) - 그룹 코드
- `group_name` (필수) - 그룹명 (`name`이 아님!)
- `price_rate` (필수) - 단가율
- `is_active` (선택) - boolean
---
## 3. API 엔드포인트 상세 (구현 완료)
### 3.1 목록 조회 `GET /api/v1/clients` ✅
**Query Parameters:**
| 파라미터 | 타입 | 설명 | 기본값 |
|---------|------|------|--------|
| `page` | int | 페이지 번호 | 1 |
| `size` | int | 페이지당 개수 | 20 |
| `q` | string | 검색어 (이름, 코드, 담당자) | - |
| `only_active` | boolean | 활성 거래처만 조회 | - |
**Response:**
```json
{
"success": true,
"message": "message.fetched",
"data": {
"current_page": 1,
"data": [
{
"id": 1,
"client_code": "CLI-001",
"name": "ABC상사",
"client_type": "매입매출",
"business_no": "123-45-67890",
"business_type": "제조업",
"business_item": "기계부품",
"contact_person": "홍길동",
"phone": "02-1234-5678",
"mobile": "010-1234-5678",
"fax": "02-1234-5679",
"email": "hong@abc.com",
"address": "서울시 강남구...",
"manager_name": "김철수",
"manager_tel": "02-1234-5680",
"purchase_payment_day": "말일",
"sales_payment_day": "말일",
"tax_agreement": false,
"bad_debt": false,
"memo": null,
"is_active": true,
"client_group": { "id": 1, "name": "VIP" }
}
],
"total": 1
}
}
```
### 3.2 단건 조회 `GET /api/v1/clients/{id}` ✅
단건 조회 시 모든 필드 포함 (목록과 동일 구조)
### 3.3 거래처 등록 `POST /api/v1/clients` ✅
**Request Body:**
```json
{
"client_code": "CLI-002",
"name": "XYZ무역",
"client_type": "매입",
"client_group_id": 1,
"business_no": "234-56-78901",
"business_type": "도소매업",
"business_item": "전자부품",
"contact_person": "이영희",
"phone": "02-2345-6789",
"mobile": "010-2345-6789",
"fax": "02-2345-6790",
"email": "lee@xyz.com",
"address": "서울시 서초구...",
"manager_name": "박민수",
"manager_tel": "02-2345-6791",
"system_manager": "관리자A",
"account_id": "xyz_user",
"account_password": "secret123",
"purchase_payment_day": "15일",
"sales_payment_day": "말일",
"tax_agreement": true,
"tax_amount": 1000000,
"tax_start_date": "2025-01-01",
"tax_end_date": "2025-12-31",
"bad_debt": false,
"memo": "신규 거래처"
}
```
### 3.4 거래처 수정 `PUT /api/v1/clients/{id}` ✅
등록과 동일한 필드 구조 (부분 수정 가능)
**주의:** `account_password`는 입력한 경우에만 업데이트됨
### 3.5 거래처 삭제 `DELETE /api/v1/clients/{id}` ✅
Hard Delete 적용 (연관 주문이 없는 경우만 삭제 가능)
### 3.6 활성/비활성 토글 `PATCH /api/v1/clients/{id}/toggle` ✅
**Response:**
```json
{
"success": true,
"message": "message.updated",
"data": {
"id": 1,
"is_active": false
}
}
```
---
## 4. 필드 구조 (7개 섹션)
### 섹션 1: 기본 정보
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| `client_code` | string | ✅ | 거래처 코드 |
| `name` | string | ✅ | 거래처명 |
| `client_type` | enum | ❌ | 매입/매출/매입매출 (기본: 매입) |
| `client_group_id` | int | ❌ | 거래처 그룹 ID |
### 섹션 2: 사업자 정보
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| `business_no` | string | ❌ | 사업자등록번호 |
| `business_type` | string | ❌ | 업태 |
| `business_item` | string | ❌ | 업종 |
### 섹션 3: 연락처 정보
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| `contact_person` | string | ❌ | 대표 담당자 |
| `phone` | string | ❌ | 전화번호 |
| `mobile` | string | ❌ | 휴대폰 |
| `fax` | string | ❌ | 팩스 |
| `email` | string | ❌ | 이메일 |
| `address` | text | ❌ | 주소 |
### 섹션 4: 담당자 정보
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| `manager_name` | string | ❌ | 담당자명 |
| `manager_tel` | string | ❌ | 담당자 전화 |
| `system_manager` | string | ❌ | 시스템 관리자 |
### 섹션 5: 발주처 설정
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| `account_id` | string | ❌ | 계정 ID |
| `account_password` | string | ❌ | 비밀번호 (hidden) |
| `purchase_payment_day` | string | ❌ | 매입 결제일 |
| `sales_payment_day` | string | ❌ | 매출 결제일 |
### 섹션 6: 약정 세금
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| `tax_agreement` | boolean | ❌ | 세금 약정 여부 |
| `tax_amount` | decimal | ❌ | 약정 금액 |
| `tax_start_date` | date | ❌ | 약정 시작일 |
| `tax_end_date` | date | ❌ | 약정 종료일 |
### 섹션 7: 악성채권 정보
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| `bad_debt` | boolean | ❌ | 악성채권 여부 |
| `bad_debt_amount` | decimal | ❌ | 악성채권 금액 |
| `bad_debt_receive_date` | date | ❌ | 채권 발생일 |
| `bad_debt_end_date` | date | ❌ | 채권 만료일 |
| `bad_debt_progress` | enum | ❌ | 진행 상태 |
### 섹션 8: 기타
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| `memo` | text | ❌ | 메모 |
| `is_active` | boolean | ❌ | 활성 상태 (기본: true) |
---
## 5. 프론트엔드 타입 정의
### 5.1 API 응답 타입
```typescript
export type ClientType = "매입" | "매출" | "매입매출";
export type BadDebtProgress = "협의중" | "소송중" | "회수완료" | "대손처리" | "";
export interface ClientApiResponse {
id: number;
tenant_id: number;
client_group_id: number | null;
client_code: string;
name: string;
client_type?: ClientType;
business_no: string | null;
business_type: string | null;
business_item: string | null;
contact_person: string | null;
phone: string | null;
mobile?: string | null;
fax?: string | null;
email: string | null;
address: string | null;
manager_name?: string | null;
manager_tel?: string | null;
system_manager?: string | null;
account_id?: string | null;
purchase_payment_day?: string | null;
sales_payment_day?: string | null;
tax_agreement?: boolean;
tax_amount?: number | null;
tax_start_date?: string | null;
tax_end_date?: string | null;
bad_debt?: boolean;
bad_debt_amount?: number | null;
bad_debt_receive_date?: string | null;
bad_debt_end_date?: string | null;
bad_debt_progress?: BadDebtProgress;
memo?: string | null;
is_active: boolean;
created_at: string;
updated_at: string;
}
```
### 5.2 프론트엔드 변환 타입
```typescript
export interface Client {
id: string;
code: string;
name: string;
businessNo: string;
representative: string;
phone: string;
address: string;
email: string;
businessType: string;
businessItem: string;
registeredDate: string;
status: "활성" | "비활성";
groupId: string | null;
clientType: ClientType;
mobile: string;
fax: string;
managerName: string;
managerTel: string;
systemManager: string;
accountId: string;
purchasePaymentDay: string;
salesPaymentDay: string;
taxAgreement: boolean;
taxAmount: string;
taxStartDate: string;
taxEndDate: string;
badDebt: boolean;
badDebtAmount: string;
badDebtReceiveDate: string;
badDebtEndDate: string;
badDebtProgress: BadDebtProgress;
memo: string;
}
```
---
## 6. 백엔드 참고 파일
### 컨트롤러/서비스
- `api/app/Http/Controllers/Api/V1/ClientController.php`
- `api/app/Http/Controllers/Api/V1/ClientGroupController.php`
- `api/app/Services/ClientService.php`
- `api/app/Services/ClientGroupService.php`
### 모델
- `api/app/Models/Orders/Client.php`
- `api/app/Models/Orders/ClientGroup.php`
### 요청 클래스
- `api/app/Http/Requests/Client/ClientStoreRequest.php`
- `api/app/Http/Requests/Client/ClientUpdateRequest.php`
### 마이그레이션
- `api/database/migrations/2025_12_04_145912_add_business_fields_to_clients_table.php`
- `api/database/migrations/2025_12_04_205603_add_extended_fields_to_clients_table.php`
### Swagger
- `api/app/Swagger/v1/ClientApi.php`
### 라우트
- `api/routes/api.php` (Line 316-333)
---
## 7. 프론트엔드 참고 파일
### 훅
- `react/src/hooks/useClientList.ts` - CRUD 훅 (530줄)
- `react/src/hooks/useClientGroupList.ts` - 그룹 CRUD 훅
### 컴포넌트
- `react/src/components/clients/ClientRegistration.tsx` - 등록/수정 폼 (sam-design 기반)
- `react/src/components/clients/ClientDetail.tsx` - 상세 보기
### 페이지
- `react/src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx` - 목록
- `react/src/app/[locale]/(protected)/sales/client-management-sales-admin/new/page.tsx` - 등록
- `react/src/app/[locale]/(protected)/sales/client-management-sales-admin/[id]/page.tsx` - 상세
- `react/src/app/[locale]/(protected)/sales/client-management-sales-admin/[id]/edit/page.tsx` - 수정
---
## 8. 추가 개선 사항 (선택)
| 항목 | 우선순위 | 상태 |
|------|---------|------|
| 거래처 그룹 관리 UI | 🟢 LOW | ⬜ 미구현 |
| 엑셀 내보내기/가져오기 | 🟢 LOW | ⬜ 미구현 |
| 거래처 검색 고급 필터 | 🟢 LOW | ⬜ 미구현 |
---
## 9. API Flow Tester 테스트 파일
### 9.1 Flow Tester 설명
API Flow Tester를 사용하여 Client 및 Client Group API의 전체 CRUD 플로우를 자동으로 테스트할 수 있습니다.
### 9.2 테스트 파일 위치
| 파일 | 설명 |
|------|------|
| `docs/front/flow-tests/client-api-flow.json` | 거래처 API CRUD 플로우 테스트 (11단계) |
| `docs/front/flow-tests/client-group-api-flow.json` | 거래처 그룹 API CRUD 플로우 테스트 (10단계) |
### 9.3 테스트 플로우 요약
#### Client API Flow (11단계)
1. 로그인 → 토큰 추출
2. 거래처 목록 조회
3. 거래처 생성
4. 거래처 단건 조회
5. 거래처 수정
6. 수정 확인 조회
7. 비활성화 토글
8. 활성화 토글
9. 거래처 검색
10. 거래처 삭제
11. 삭제 확인 (404 예상)
#### Client Group API Flow (10단계)
1. 로그인 → 토큰 추출
2. 그룹 목록 조회
3. 그룹 생성 (`group_code`, `group_name`, `price_rate` 필수)
4. 그룹 단건 조회
5. 그룹 수정
6. 수정 확인 조회
7. 비활성화 토글
8. 활성화 토글
9. 그룹 삭제
10. 삭제 확인 (404 예상)
### 9.4 Flow Tester 사용 시 주의사항
**환경변수 설정 필요:**
```bash
FLOW_TESTER_USER_ID=테스트계정ID
FLOW_TESTER_USER_PWD=테스트계정비밀번호
```
**API 응답 구조 차이점:**
- `/login` 응답: `{ "access_token": "...", "token_type": "..." }` (루트 레벨)
- 기타 API 응답: `{ "success": true, "data": {...} }` (data 래핑)
**필드 타입 주의:**
- `is_active`: boolean (`true`/`false`), 문자열 `"Y"`/`"N"` 아님
- `price_rate`: numeric (0~99.9999)
---
**최종 업데이트**: 2025-12-08