diff --git a/INDEX.md b/INDEX.md
index 0a83317..f3bcb05 100644
--- a/INDEX.md
+++ b/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/ 체κ³ν
diff --git a/front/[API-2025-12-04] client-api-analysis.md b/front/[API-2025-12-04] client-api-analysis.md
index 52358e9..62f5542 100644
--- a/front/[API-2025-12-04] client-api-analysis.md
+++ b/front/[API-2025-12-04] client-api-analysis.md
@@ -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 νμν μμ
diff --git a/front/[API-2025-12-04] quote-api-request.md b/front/[API-2025-12-04] quote-api-request.md
index d3154f1..ebe9174 100644
--- a/front/[API-2025-12-04] quote-api-request.md
+++ b/front/[API-2025-12-04] quote-api-request.md
@@ -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,
diff --git a/principles/README.md b/principles/README.md
new file mode 100644
index 0000000..7751091
--- /dev/null
+++ b/principles/README.md
@@ -0,0 +1,24 @@
+# Principles (μ€κ³ μμΉ)
+
+> μμ€ν
μ€κ³μ μν€ν
μ² κ²°μ μ κ·Όκ°μ΄ λλ μμΉ
+
+## λͺ©μ
+- μΌκ΄λ μν€ν
μ² κ²°μ κΈ°μ€ μ 곡
+- κΈ°μ λΆμ± λ°©μ§
+- νμ₯μ±κ³Ό μ μ§λ³΄μμ± ν보
+
+## ν¬ν¨ λ΄μ©
+- μν€ν
μ² μμΉ (Service-First, Multi-tenancy)
+- API μ€κ³ μμΉ (RESTful, λ²μ κ΄λ¦¬)
+- λ°μ΄ν°λ² μ΄μ€ μ€κ³ μμΉ
+- 보μ μ€κ³ μμΉ
+- μ±λ₯ μ΅μ ν μμΉ
+
+## λ¬Έμ λͺ©λ‘
+| λ¬Έμ | μ€λͺ
|
+|------|------|
+| *(μμ± μμ )* | |
+
+## κ΄λ ¨ λ¬Έμ
+- [μμ€ν
μν€ν
μ²](../reference/architecture.md) - μ 체 μμ€ν
ꡬ쑰
+- [API κ°λ° κ·μΉ](../reference/api-rules.md) - API μ€κ³ νμ€
\ No newline at end of file
diff --git a/reference/api-rules.md b/reference/api-rules.md
index b124965..2a775e5 100644
--- a/reference/api-rules.md
+++ b/reference/api-rules.md
@@ -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
diff --git a/rules/README.md b/rules/README.md
new file mode 100644
index 0000000..d5f7af2
--- /dev/null
+++ b/rules/README.md
@@ -0,0 +1,24 @@
+# Rules (λΉμ¦λμ€ κ·μΉ)
+
+> λλ©μΈ λ‘μ§κ³Ό λΉμ¦λμ€ μꡬμ¬νμμ νμλ κ·μΉ
+
+## λͺ©μ
+- λΉμ¦λμ€ λ‘μ§μ λͺ
νν λ¬Έμν
+- κ°λ° μ κ·μΉ κΈ°λ° κ΅¬ν κ°μ΄λ
+- κ²μ¦ λ‘μ§μ κ·Όκ±° μ 곡
+
+## ν¬ν¨ λ΄μ©
+- νλͺ©μ½λ μμ± κ·μΉ
+- νλ κ²μ¦ κ·μΉ
+- μν μ μ΄ κ·μΉ
+- κΆν/μ κ·Ό μ μ΄ κ·μΉ
+- λ°μ΄ν° λ¬΄κ²°μ± κ·μΉ
+
+## λ¬Έμ λͺ©λ‘
+| λ¬Έμ | μ€λͺ
|
+|------|------|
+| *(μμ± μμ )* | |
+
+## κ΄λ ¨ λ¬Έμ
+- [νλͺ©κΈ°μ€κ΄λ¦¬ μ€ν](../specs/item-master-integration.md) - νλͺ© κ΄λ¦¬ λͺ
μΈ
+- [보μ μ μ±
](../specs/security-policy.md) - 보μ κ΄λ ¨ κ·μΉ
\ No newline at end of file
diff --git a/specs/item-master-integration.md b/specs/item-master-integration.md
new file mode 100644
index 0000000..60ab2cf
--- /dev/null
+++ b/specs/item-master-integration.md
@@ -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 β
+ field_type: number β
+ field_type: dropdown β
+ field_type: checkbox β
+ field_type: date β
+ field_type: 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;
+}
+
+function DynamicFieldRenderer({ field, value, onChange }: Props) {
+ switch (field.field_type) {
+ case 'textbox':
+ return (
+ onChange(field.field_key, e.target.value)}
+ placeholder={field.placeholder}
+ required={field.is_required}
+ />
+ );
+
+ case 'number':
+ return (
+ onChange(field.field_key, val)}
+ min={field.validation_rules?.min}
+ max={field.validation_rules?.max}
+ required={field.is_required}
+ />
+ );
+
+ case 'dropdown':
+ return (
+