docs: 규칙 및 원칙 문서 체계 추가

- standards/ 폴더 추가 (코딩 규칙: 네이밍, 스타일)
- rules/ 폴더 추가 (비즈니스 규칙: 검증, 도메인 로직)
- principles/ 폴더 추가 (설계 원칙: 아키텍처, API 설계)
- INDEX.md에 규칙 및 원칙 섹션 추가
- 각 폴더에 README.md 생성
This commit is contained in:
2025-12-05 17:57:47 +09:00
parent 08a8259313
commit 02a27f2bba
8 changed files with 886 additions and 26 deletions

View File

@@ -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 (일상 참고 문서) ### Reference (일상 참고 문서)
@@ -129,6 +146,11 @@
## 🔄 문서 구조 변경 이력 ## 🔄 문서 구조 변경 이력
- **2025-12-05**: 규칙 및 원칙 문서 체계 추가
- standards/ - 코딩 규칙 (네이밍, 스타일)
- rules/ - 비즈니스 규칙 (검증, 도메인 로직)
- principles/ - 설계 원칙 (아키텍처, API 설계)
- **2025-11-20**: 문서 구조 대규모 재정리 - **2025-11-20**: 문서 구조 대규모 재정리
- .cursor/docs 삭제 - .cursor/docs 삭제
- claudedocs → docs/ 체계화 - claudedocs → docs/ 체계화

View File

@@ -96,36 +96,124 @@ protected $fillable = [
## 4. 백엔드 수정 요청 사항 ## 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
프론트엔드에서 사용하는 사업자 정보 필드가 백엔드에 없음
### 수정 요청 > **참고**: `sam-design/src/components/ClientRegistration.tsx` 기준으로 UI 구현 필요
다음 필드를 Client 모델에 추가: > 현재 백엔드 API에 누락된 필드들 추가 요청
#### 섹션 1: 기본 정보 추가 필드
| 필드명 | 타입 | 설명 | nullable | 비고 |
|--------|------|------|----------|------|
| `client_type` | enum('매입','매출','매입매출') | 거래처 유형 | NO | 기본값 '매입' |
#### 섹션 2: 연락처 정보 추가 필드
| 필드명 | 타입 | 설명 | nullable | | 필드명 | 타입 | 설명 | nullable |
|--------|------|------|----------| |--------|------|------|----------|
| `business_no` | string(20) | 사업자등록번호 | nullable | | `mobile` | string(20) | 모바일 번호 | YES |
| `business_type` | string(50) | 업태 | nullable | | `fax` | string(20) | 팩스 번호 | YES |
| `business_item` | string(100) | 업종 | nullable |
#### 섹션 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 ```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 client_type ENUM('매입','매출','매입매출') DEFAULT '매입';
ALTER TABLE clients ADD COLUMN business_item VARCHAR(100) NULL;
``` -- 연락처 정보
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. 프론트엔드 API 연동 구현 계획
### 5.1 필요한 작업 ### 5.1 필요한 작업

View File

@@ -187,7 +187,15 @@ interface QuoteRevision {
| `GET` | `/api/v1/quotes/{id}/document/calculation` | 산출내역서 PDF | | | `GET` | `/api/v1/quotes/{id}/document/calculation` | 산출내역서 PDF | |
| `GET` | `/api/v1/quotes/{id}/document/purchase-order` | 발주서 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 | 설명 | 비고 | | 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 ```typescript
// useQuoteList 훅 (목록)
const { const {
quotes, quotes,
pagination, pagination,
@@ -542,10 +616,8 @@ const {
deleteQuote, deleteQuote,
bulkDelete bulkDelete
} = useQuoteList(); } = useQuoteList();
```
### 7.2 useQuote 훅 (단건 CRUD) // useQuote 훅 (단건 CRUD)
```typescript
const { const {
quote, quote,
isLoading, isLoading,
@@ -555,10 +627,8 @@ const {
finalizeQuote, finalizeQuote,
convertToOrder convertToOrder
} = useQuote(); } = useQuote();
```
### 7.3 useQuoteCalculation 훅 (자동 산출) // useQuoteCalculation 훅 (자동 산출)
```typescript
const { const {
calculationResult, calculationResult,
isCalculating, isCalculating,

24
principles/README.md Normal file
View File

@@ -0,0 +1,24 @@
# Principles (설계 원칙)
> 시스템 설계와 아키텍처 결정의 근간이 되는 원칙
## 목적
- 일관된 아키텍처 결정 기준 제공
- 기술 부채 방지
- 확장성과 유지보수성 확보
## 포함 내용
- 아키텍처 원칙 (Service-First, Multi-tenancy)
- API 설계 원칙 (RESTful, 버전 관리)
- 데이터베이스 설계 원칙
- 보안 설계 원칙
- 성능 최적화 원칙
## 문서 목록
| 문서 | 설명 |
|------|------|
| *(작성 예정)* | |
## 관련 문서
- [시스템 아키텍처](../reference/architecture.md) - 전체 시스템 구조
- [API 개발 규칙](../reference/api-rules.md) - API 설계 표준

View File

@@ -21,6 +21,60 @@
- Common columns: tenant_id, created_by, updated_by, deleted_by (COMMENT required) - Common columns: tenant_id, created_by, updated_by, deleted_by (COMMENT required)
- FK constraints: Created during design, minimal in production - 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 ## 3. Middleware Stack

24
rules/README.md Normal file
View File

@@ -0,0 +1,24 @@
# Rules (비즈니스 규칙)
> 도메인 로직과 비즈니스 요구사항에서 파생된 규칙
## 목적
- 비즈니스 로직의 명확한 문서화
- 개발 시 규칙 기반 구현 가이드
- 검증 로직의 근거 제공
## 포함 내용
- 품목코드 생성 규칙
- 필드 검증 규칙
- 상태 전이 규칙
- 권한/접근 제어 규칙
- 데이터 무결성 규칙
## 문서 목록
| 문서 | 설명 |
|------|------|
| *(작성 예정)* | |
## 관련 문서
- [품목기준관리 스펙](../specs/item-master-integration.md) - 품목 관리 명세
- [보안 정책](../specs/security-policy.md) - 보안 관련 규칙

View 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
View File

@@ -0,0 +1,23 @@
# Standards (코딩 규칙)
> 개발 시 준수해야 할 코딩 컨벤션과 스타일 가이드
## 목적
- 일관된 코드 스타일 유지
- 팀 간 협업 효율성 향상
- 코드 리뷰 기준 명확화
## 포함 내용
- 네이밍 컨벤션 (변수, 함수, 클래스, 파일)
- 코드 스타일 (들여쓰기, 주석, 포맷팅)
- 디렉토리 구조 규칙
- 테스트 작성 규칙
## 문서 목록
| 문서 | 설명 |
|------|------|
| *(작성 예정)* | |
## 관련 문서
- [API 개발 규칙](../reference/api-rules.md) - API 개발 표준
- [Git 규칙](../reference/git-conventions.md) - 커밋 메시지, 브랜치 전략