docs: 규칙 및 원칙 문서 체계 추가
- standards/ 폴더 추가 (코딩 규칙: 네이밍, 스타일) - rules/ 폴더 추가 (비즈니스 규칙: 검증, 도메인 로직) - principles/ 폴더 추가 (설계 원칙: 아키텍처, API 설계) - INDEX.md에 규칙 및 원칙 섹션 추가 - 각 폴더에 README.md 생성
This commit is contained in:
22
INDEX.md
22
INDEX.md
@@ -14,6 +14,23 @@
|
||||
|
||||
---
|
||||
|
||||
## 📐 규칙 및 원칙
|
||||
|
||||
개발 시 참고해야 할 규칙과 원칙 문서입니다.
|
||||
|
||||
| 규칙 유형 | 폴더 | 용도 | 예시 |
|
||||
|----------|------|------|------|
|
||||
| **코딩 규칙** | [standards/](standards/README.md) | 네이밍 컨벤션, 코드 스타일 | 변수명, 파일명, 포맷팅 |
|
||||
| **비즈니스 규칙** | [rules/](rules/README.md) | 도메인 로직에서 파생된 규칙 | 품목코드 생성, 검증 규칙 |
|
||||
| **설계 원칙** | [principles/](principles/README.md) | 아키텍처/설계 결정 기준 | Service-First, API 설계 |
|
||||
|
||||
### 문서 분류 기준
|
||||
- **Standards**: "어떻게 코드를 작성할 것인가" (How to write)
|
||||
- **Rules**: "무엇이 유효한 데이터/상태인가" (What is valid)
|
||||
- **Principles**: "왜 이렇게 설계하는가" (Why we design)
|
||||
|
||||
---
|
||||
|
||||
## 📖 개발 가이드
|
||||
|
||||
### Reference (일상 참고 문서)
|
||||
@@ -129,6 +146,11 @@
|
||||
|
||||
## 🔄 문서 구조 변경 이력
|
||||
|
||||
- **2025-12-05**: 규칙 및 원칙 문서 체계 추가
|
||||
- standards/ - 코딩 규칙 (네이밍, 스타일)
|
||||
- rules/ - 비즈니스 규칙 (검증, 도메인 로직)
|
||||
- principles/ - 설계 원칙 (아키텍처, API 설계)
|
||||
|
||||
- **2025-11-20**: 문서 구조 대규모 재정리
|
||||
- .cursor/docs 삭제
|
||||
- claudedocs → docs/ 체계화
|
||||
|
||||
@@ -96,36 +96,124 @@ protected $fillable = [
|
||||
|
||||
## 4. 백엔드 수정 요청 사항
|
||||
|
||||
### 4.1 Client 모델 필드 추가 요청
|
||||
### 4.1 1차 필드 추가 ✅ 완료 (2025-12-04)
|
||||
|
||||
```markdown
|
||||
## 백엔드 API 수정 요청
|
||||
| 필드명 | 타입 | 설명 | 상태 |
|
||||
|--------|------|------|------|
|
||||
| `business_no` | string(20) | 사업자등록번호 | ✅ 추가됨 |
|
||||
| `business_type` | string(50) | 업태 | ✅ 추가됨 |
|
||||
| `business_item` | string(100) | 업종 | ✅ 추가됨 |
|
||||
|
||||
### 파일 위치
|
||||
`app/Models/Orders/Client.php` - Client 모델
|
||||
---
|
||||
|
||||
### 현재 문제
|
||||
프론트엔드에서 사용하는 사업자 정보 필드가 백엔드에 없음
|
||||
### 4.2 🚨 2차 필드 추가 요청 (sam-design 기준) - 2025-12-04
|
||||
|
||||
### 수정 요청
|
||||
다음 필드를 Client 모델에 추가:
|
||||
> **참고**: `sam-design/src/components/ClientRegistration.tsx` 기준으로 UI 구현 필요
|
||||
> 현재 백엔드 API에 누락된 필드들 추가 요청
|
||||
|
||||
#### 섹션 1: 기본 정보 추가 필드
|
||||
| 필드명 | 타입 | 설명 | nullable | 비고 |
|
||||
|--------|------|------|----------|------|
|
||||
| `client_type` | enum('매입','매출','매입매출') | 거래처 유형 | NO | 기본값 '매입' |
|
||||
|
||||
#### 섹션 2: 연락처 정보 추가 필드
|
||||
| 필드명 | 타입 | 설명 | nullable |
|
||||
|--------|------|------|----------|
|
||||
| `business_no` | string(20) | 사업자등록번호 | nullable |
|
||||
| `business_type` | string(50) | 업태 | nullable |
|
||||
| `business_item` | string(100) | 업종 | nullable |
|
||||
| `mobile` | string(20) | 모바일 번호 | YES |
|
||||
| `fax` | string(20) | 팩스 번호 | YES |
|
||||
|
||||
#### 섹션 3: 담당자 정보 추가 필드
|
||||
| 필드명 | 타입 | 설명 | nullable |
|
||||
|--------|------|------|----------|
|
||||
| `manager_name` | string(50) | 담당자명 | YES |
|
||||
| `manager_tel` | string(20) | 담당자 전화 | YES |
|
||||
| `system_manager` | string(50) | 시스템 관리자 | YES |
|
||||
|
||||
#### 섹션 4: 발주처 설정 추가 필드
|
||||
| 필드명 | 타입 | 설명 | nullable |
|
||||
|--------|------|------|----------|
|
||||
| `account_id` | string(50) | 계정 ID | YES |
|
||||
| `account_password` | string(255) | 비밀번호 (암호화) | YES |
|
||||
| `purchase_payment_day` | string(20) | 매입 결제일 | YES |
|
||||
| `sales_payment_day` | string(20) | 매출 결제일 | YES |
|
||||
|
||||
#### 섹션 5: 약정 세금 추가 필드
|
||||
| 필드명 | 타입 | 설명 | nullable |
|
||||
|--------|------|------|----------|
|
||||
| `tax_agreement` | boolean | 세금 약정 여부 | YES |
|
||||
| `tax_amount` | decimal(15,2) | 약정 금액 | YES |
|
||||
| `tax_start_date` | date | 약정 시작일 | YES |
|
||||
| `tax_end_date` | date | 약정 종료일 | YES |
|
||||
|
||||
#### 섹션 6: 악성채권 정보 추가 필드
|
||||
| 필드명 | 타입 | 설명 | nullable |
|
||||
|--------|------|------|----------|
|
||||
| `bad_debt` | boolean | 악성채권 여부 | YES |
|
||||
| `bad_debt_amount` | decimal(15,2) | 악성채권 금액 | YES |
|
||||
| `bad_debt_receive_date` | date | 채권 발생일 | YES |
|
||||
| `bad_debt_end_date` | date | 채권 만료일 | YES |
|
||||
| `bad_debt_progress` | enum('협의중','소송중','회수완료','대손처리') | 진행 상태 | YES |
|
||||
|
||||
#### 섹션 7: 기타 정보 추가 필드
|
||||
| 필드명 | 타입 | 설명 | nullable |
|
||||
|--------|------|------|----------|
|
||||
| `memo` | text | 메모 | YES |
|
||||
|
||||
---
|
||||
|
||||
### 4.3 마이그레이션 예시
|
||||
|
||||
### 마이그레이션 예시
|
||||
```sql
|
||||
ALTER TABLE clients ADD COLUMN business_no VARCHAR(20) NULL;
|
||||
ALTER TABLE clients ADD COLUMN business_type VARCHAR(50) NULL;
|
||||
ALTER TABLE clients ADD COLUMN business_item VARCHAR(100) NULL;
|
||||
```
|
||||
-- 기본 정보
|
||||
ALTER TABLE clients ADD COLUMN client_type ENUM('매입','매출','매입매출') DEFAULT '매입';
|
||||
|
||||
-- 연락처 정보
|
||||
ALTER TABLE clients ADD COLUMN mobile VARCHAR(20) NULL;
|
||||
ALTER TABLE clients ADD COLUMN fax VARCHAR(20) NULL;
|
||||
|
||||
-- 담당자 정보
|
||||
ALTER TABLE clients ADD COLUMN manager_name VARCHAR(50) NULL;
|
||||
ALTER TABLE clients ADD COLUMN manager_tel VARCHAR(20) NULL;
|
||||
ALTER TABLE clients ADD COLUMN system_manager VARCHAR(50) NULL;
|
||||
|
||||
-- 발주처 설정
|
||||
ALTER TABLE clients ADD COLUMN account_id VARCHAR(50) NULL;
|
||||
ALTER TABLE clients ADD COLUMN account_password VARCHAR(255) NULL;
|
||||
ALTER TABLE clients ADD COLUMN purchase_payment_day VARCHAR(20) NULL;
|
||||
ALTER TABLE clients ADD COLUMN sales_payment_day VARCHAR(20) NULL;
|
||||
|
||||
-- 약정 세금
|
||||
ALTER TABLE clients ADD COLUMN tax_agreement TINYINT(1) DEFAULT 0;
|
||||
ALTER TABLE clients ADD COLUMN tax_amount DECIMAL(15,2) NULL;
|
||||
ALTER TABLE clients ADD COLUMN tax_start_date DATE NULL;
|
||||
ALTER TABLE clients ADD COLUMN tax_end_date DATE NULL;
|
||||
|
||||
-- 악성채권 정보
|
||||
ALTER TABLE clients ADD COLUMN bad_debt TINYINT(1) DEFAULT 0;
|
||||
ALTER TABLE clients ADD COLUMN bad_debt_amount DECIMAL(15,2) NULL;
|
||||
ALTER TABLE clients ADD COLUMN bad_debt_receive_date DATE NULL;
|
||||
ALTER TABLE clients ADD COLUMN bad_debt_end_date DATE NULL;
|
||||
ALTER TABLE clients ADD COLUMN bad_debt_progress ENUM('협의중','소송중','회수완료','대손처리') NULL;
|
||||
|
||||
-- 기타 정보
|
||||
ALTER TABLE clients ADD COLUMN memo TEXT NULL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.4 수정 필요 파일 목록
|
||||
|
||||
| 파일 | 수정 내용 |
|
||||
|------|----------|
|
||||
| `app/Models/Orders/Client.php` | fillable에 새 필드 추가, casts 설정 |
|
||||
| `database/migrations/xxxx_add_client_extended_fields.php` | 마이그레이션 생성 |
|
||||
| `app/Services/ClientService.php` | 새 필드 처리 로직 추가 |
|
||||
| `app/Http/Requests/Client/ClientStoreRequest.php` | 유효성 검증 규칙 추가 |
|
||||
| `app/Http/Requests/Client/ClientUpdateRequest.php` | 유효성 검증 규칙 추가 |
|
||||
| `app/Swagger/v1/ClientApi.php` | API 문서 업데이트 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 프론트엔드 API 연동 구현 계획
|
||||
|
||||
### 5.1 필요한 작업
|
||||
|
||||
@@ -187,7 +187,15 @@ interface QuoteRevision {
|
||||
| `GET` | `/api/v1/quotes/{id}/document/calculation` | 산출내역서 PDF | |
|
||||
| `GET` | `/api/v1/quotes/{id}/document/purchase-order` | 발주서 PDF | |
|
||||
|
||||
### 3.5 견적번호 생성
|
||||
### 3.5 문서 발송 API ⭐ 신규 요청
|
||||
|
||||
| Method | Endpoint | 설명 | 비고 |
|
||||
|--------|----------|------|------|
|
||||
| `POST` | `/api/v1/quotes/{id}/send/email` | 이메일 발송 | 첨부파일 포함 |
|
||||
| `POST` | `/api/v1/quotes/{id}/send/fax` | 팩스 발송 | 팩스 서비스 연동 |
|
||||
| `POST` | `/api/v1/quotes/{id}/send/kakao` | 카카오톡 발송 | 알림톡/친구톡 |
|
||||
|
||||
### 3.6 견적번호 생성
|
||||
|
||||
| Method | Endpoint | 설명 | 비고 |
|
||||
|--------|----------|------|------|
|
||||
@@ -530,10 +538,76 @@ sort_order: 'asc' | 'desc'
|
||||
|
||||
---
|
||||
|
||||
## 7. 프론트엔드 연동 계획
|
||||
## 7. 프론트엔드 구현 현황 (2025-12-04 업데이트)
|
||||
|
||||
### 7.1 구현 완료된 파일
|
||||
|
||||
| 파일 | 설명 | 상태 |
|
||||
|------|------|------|
|
||||
| `quote-management/page.tsx` | 견적 목록 페이지 | ✅ 완료 (샘플 데이터) |
|
||||
| `quote-management/new/page.tsx` | 견적 등록 페이지 | ✅ 완료 |
|
||||
| `quote-management/[id]/page.tsx` | 견적 상세 페이지 | ✅ 완료 |
|
||||
| `quote-management/[id]/edit/page.tsx` | 견적 수정 페이지 | ✅ 완료 |
|
||||
| `components/quotes/QuoteRegistration.tsx` | 견적 등록/수정 컴포넌트 | ✅ 완료 |
|
||||
| `components/quotes/QuoteDocument.tsx` | 견적서 문서 컴포넌트 | ✅ 완료 |
|
||||
| `components/quotes/QuoteCalculationReport.tsx` | 산출내역서 문서 컴포넌트 | ✅ 완료 |
|
||||
| `components/quotes/PurchaseOrderDocument.tsx` | 발주서 문서 컴포넌트 | ✅ 완료 |
|
||||
|
||||
### 7.2 UI 기능 구현 현황
|
||||
|
||||
| 기능 | 상태 | 비고 |
|
||||
|------|------|------|
|
||||
| 견적 목록 조회 | ✅ UI 완료 | 샘플 데이터, API 연동 필요 |
|
||||
| 견적 검색/필터 | ✅ UI 완료 | 로컬 필터링, API 연동 필요 |
|
||||
| 견적 등록 폼 | ✅ UI 완료 | API 연동 필요 |
|
||||
| 견적 상세 페이지 | ✅ UI 완료 | API 연동 필요 |
|
||||
| 견적 수정 폼 | ✅ UI 완료 | API 연동 필요 |
|
||||
| 견적 삭제 | ✅ UI 완료 | 로컬 상태, API 연동 필요 |
|
||||
| 견적 일괄 삭제 | ✅ UI 완료 | 로컬 상태, API 연동 필요 |
|
||||
| 자동 견적 산출 | ⏳ 버튼만 | 백엔드 수식 엔진 필요 |
|
||||
| 발주처 선택 | ⏳ 샘플 데이터 | `/api/v1/clients` 연동 필요 |
|
||||
| 현장 선택 | ⏳ 샘플 데이터 | 발주처 연동 후 현장 API 필요 |
|
||||
| 제품 선택 | ⏳ 샘플 데이터 | `/api/v1/item-masters` 연동 필요 |
|
||||
| **견적서 모달** | ✅ UI 완료 | PDF/이메일/팩스/카톡 버튼, **발송 API 필요** |
|
||||
| **산출내역서 모달** | ✅ UI 완료 | PDF/이메일/팩스/카톡 버튼, **발송 API 필요** |
|
||||
| **발주서 모달** | ✅ UI 완료 | PDF/이메일/팩스/카톡 버튼, **발송 API 필요** |
|
||||
| 최종확정 버튼 | ✅ UI 완료 | API 연동 필요 |
|
||||
|
||||
### 7.3 견적 등록/수정 폼 필드 (구현 완료)
|
||||
|
||||
**기본 정보 섹션:**
|
||||
- 등록일 (readonly, 오늘 날짜)
|
||||
- 작성자 (readonly, 로그인 사용자)
|
||||
- 발주처 선택 * (필수)
|
||||
- 현장명 (발주처 선택 시 연동)
|
||||
- 발주 담당자
|
||||
- 연락처
|
||||
- 납기일
|
||||
- 비고
|
||||
|
||||
**자동 견적 산출 섹션 (동적 항목):**
|
||||
- 층수
|
||||
- 부호
|
||||
- 제품 카테고리 (PC) *
|
||||
- 제품명 *
|
||||
- 오픈사이즈 (W0) *
|
||||
- 오픈사이즈 (H0) *
|
||||
- 가이드레일 설치 유형 (GT) *
|
||||
- 모터 전원 (MP) *
|
||||
- 연동제어기 (CT) *
|
||||
- 수량 (QTY) *
|
||||
- 마구리 날개치수 (WS)
|
||||
- 검사비 (INSP)
|
||||
|
||||
**기능:**
|
||||
- 견적 항목 추가/복사/삭제
|
||||
- 자동 견적 산출 버튼
|
||||
- 샘플 데이터 생성 버튼
|
||||
|
||||
### 7.4 다음 단계 (API 연동)
|
||||
|
||||
### 7.1 useQuoteList 훅 (목록)
|
||||
```typescript
|
||||
// useQuoteList 훅 (목록)
|
||||
const {
|
||||
quotes,
|
||||
pagination,
|
||||
@@ -542,10 +616,8 @@ const {
|
||||
deleteQuote,
|
||||
bulkDelete
|
||||
} = useQuoteList();
|
||||
```
|
||||
|
||||
### 7.2 useQuote 훅 (단건 CRUD)
|
||||
```typescript
|
||||
// useQuote 훅 (단건 CRUD)
|
||||
const {
|
||||
quote,
|
||||
isLoading,
|
||||
@@ -555,10 +627,8 @@ const {
|
||||
finalizeQuote,
|
||||
convertToOrder
|
||||
} = useQuote();
|
||||
```
|
||||
|
||||
### 7.3 useQuoteCalculation 훅 (자동 산출)
|
||||
```typescript
|
||||
// useQuoteCalculation 훅 (자동 산출)
|
||||
const {
|
||||
calculationResult,
|
||||
isCalculating,
|
||||
|
||||
24
principles/README.md
Normal file
24
principles/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Principles (설계 원칙)
|
||||
|
||||
> 시스템 설계와 아키텍처 결정의 근간이 되는 원칙
|
||||
|
||||
## 목적
|
||||
- 일관된 아키텍처 결정 기준 제공
|
||||
- 기술 부채 방지
|
||||
- 확장성과 유지보수성 확보
|
||||
|
||||
## 포함 내용
|
||||
- 아키텍처 원칙 (Service-First, Multi-tenancy)
|
||||
- API 설계 원칙 (RESTful, 버전 관리)
|
||||
- 데이터베이스 설계 원칙
|
||||
- 보안 설계 원칙
|
||||
- 성능 최적화 원칙
|
||||
|
||||
## 문서 목록
|
||||
| 문서 | 설명 |
|
||||
|------|------|
|
||||
| *(작성 예정)* | |
|
||||
|
||||
## 관련 문서
|
||||
- [시스템 아키텍처](../reference/architecture.md) - 전체 시스템 구조
|
||||
- [API 개발 규칙](../reference/api-rules.md) - API 설계 표준
|
||||
@@ -21,6 +21,60 @@
|
||||
- Common columns: tenant_id, created_by, updated_by, deleted_by (COMMENT required)
|
||||
- FK constraints: Created during design, minimal in production
|
||||
|
||||
### 2.1 ModelTrait 사용 가이드
|
||||
|
||||
`ModelTrait`는 모든 모델에서 공통으로 사용하는 기능을 제공합니다.
|
||||
|
||||
**위치**: `app/Traits/ModelTrait.php`
|
||||
|
||||
**제공 기능**:
|
||||
```php
|
||||
// 1. 날짜 직렬화 포맷 (Y-m-d H:i:s)
|
||||
protected function serializeDate(DateTimeInterface $date)
|
||||
|
||||
// 2. Active 상태 조회 Scope
|
||||
public function scopeActive($query)
|
||||
// 사용: Model::active()->get()
|
||||
// SQL: WHERE is_active = 1
|
||||
```
|
||||
|
||||
**⚠️ 필수 요구사항**:
|
||||
|
||||
`scopeActive()` 메서드 사용 시 테이블에 `is_active` 컬럼이 **반드시 존재해야 함**
|
||||
|
||||
```sql
|
||||
-- 마이그레이션 예시
|
||||
$table->boolean('is_active')
|
||||
->default(true)
|
||||
->comment('활성화 여부 (ModelTrait::scopeActive() 사용)');
|
||||
```
|
||||
|
||||
**모델 설정**:
|
||||
```php
|
||||
class YourModel extends Model
|
||||
{
|
||||
use BelongsToTenant, ModelTrait, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
// ...
|
||||
'is_active', // 반드시 추가
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean', // 반드시 추가
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
**is_active 컬럼 적용 테이블** (2025-12-05 기준):
|
||||
| 테이블 | is_active | 비고 |
|
||||
|--------|-----------|------|
|
||||
| materials | ✅ 있음 | |
|
||||
| products | ✅ 있음 | |
|
||||
| item_pages | ✅ 있음 | |
|
||||
| item_fields | ✅ 있음 | 2025-12-05 추가 |
|
||||
| item_sections | ❌ 없음 | 필요시 마이그레이션 추가 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Middleware Stack
|
||||
|
||||
24
rules/README.md
Normal file
24
rules/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Rules (비즈니스 규칙)
|
||||
|
||||
> 도메인 로직과 비즈니스 요구사항에서 파생된 규칙
|
||||
|
||||
## 목적
|
||||
- 비즈니스 로직의 명확한 문서화
|
||||
- 개발 시 규칙 기반 구현 가이드
|
||||
- 검증 로직의 근거 제공
|
||||
|
||||
## 포함 내용
|
||||
- 품목코드 생성 규칙
|
||||
- 필드 검증 규칙
|
||||
- 상태 전이 규칙
|
||||
- 권한/접근 제어 규칙
|
||||
- 데이터 무결성 규칙
|
||||
|
||||
## 문서 목록
|
||||
| 문서 | 설명 |
|
||||
|------|------|
|
||||
| *(작성 예정)* | |
|
||||
|
||||
## 관련 문서
|
||||
- [품목기준관리 스펙](../specs/item-master-integration.md) - 품목 관리 명세
|
||||
- [보안 정책](../specs/security-policy.md) - 보안 관련 규칙
|
||||
555
specs/item-master-integration.md
Normal file
555
specs/item-master-integration.md
Normal file
@@ -0,0 +1,555 @@
|
||||
# ItemMaster 연동 설계서
|
||||
|
||||
**작성일**: 2025-12-05
|
||||
**버전**: 1.0
|
||||
**상태**: 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, is_active │
|
||||
│ │
|
||||
│ item_type: FG(완제품), PT(반제품), 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 ← 활성 여부 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 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. 구현 계획
|
||||
|
||||
| 순서 | 작업 | 담당 | 예상 공수 |
|
||||
|------|------|------|----------|
|
||||
| 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일**
|
||||
|
||||
---
|
||||
|
||||
## 9. 변경 이력
|
||||
|
||||
| 날짜 | 버전 | 변경 내용 | 작성자 |
|
||||
|------|------|----------|--------|
|
||||
| 2025-12-05 | 1.0 | 최초 작성 | - |
|
||||
23
standards/README.md
Normal file
23
standards/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Standards (코딩 규칙)
|
||||
|
||||
> 개발 시 준수해야 할 코딩 컨벤션과 스타일 가이드
|
||||
|
||||
## 목적
|
||||
- 일관된 코드 스타일 유지
|
||||
- 팀 간 협업 효율성 향상
|
||||
- 코드 리뷰 기준 명확화
|
||||
|
||||
## 포함 내용
|
||||
- 네이밍 컨벤션 (변수, 함수, 클래스, 파일)
|
||||
- 코드 스타일 (들여쓰기, 주석, 포맷팅)
|
||||
- 디렉토리 구조 규칙
|
||||
- 테스트 작성 규칙
|
||||
|
||||
## 문서 목록
|
||||
| 문서 | 설명 |
|
||||
|------|------|
|
||||
| *(작성 예정)* | |
|
||||
|
||||
## 관련 문서
|
||||
- [API 개발 규칙](../reference/api-rules.md) - API 개발 표준
|
||||
- [Git 규칙](../reference/git-conventions.md) - 커밋 메시지, 브랜치 전략
|
||||
Reference in New Issue
Block a user