375 lines
9.4 KiB
Markdown
375 lines
9.4 KiB
Markdown
|
|
# 수주 등록 컴포넌트
|
||
|
|
|
||
|
|
## 개요
|
||
|
|
|
||
|
|
수주 등록 기능을 위한 전용 컴포넌트들입니다. 이 컴포넌트들은 범용 컴포넌트를 래핑하여 수주 등록에 최적화된 고정 설정을 제공합니다.
|
||
|
|
|
||
|
|
## 컴포넌트 구조
|
||
|
|
|
||
|
|
```
|
||
|
|
frontend/components/order/
|
||
|
|
├── OrderRegistrationModal.tsx # 수주 등록 메인 모달
|
||
|
|
├── OrderCustomerSearch.tsx # 거래처 검색 (전용)
|
||
|
|
├── OrderItemRepeaterTable.tsx # 품목 반복 테이블 (전용)
|
||
|
|
└── README.md # 문서 (현재 파일)
|
||
|
|
```
|
||
|
|
|
||
|
|
## 1. OrderRegistrationModal
|
||
|
|
|
||
|
|
수주 등록 메인 모달 컴포넌트입니다.
|
||
|
|
|
||
|
|
### Props
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface OrderRegistrationModalProps {
|
||
|
|
/** 모달 열림/닫힘 상태 */
|
||
|
|
open: boolean;
|
||
|
|
/** 모달 상태 변경 핸들러 */
|
||
|
|
onOpenChange: (open: boolean) => void;
|
||
|
|
/** 수주 등록 성공 시 콜백 */
|
||
|
|
onSuccess?: () => void;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 사용 예시
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
import { OrderRegistrationModal } from "@/components/order/OrderRegistrationModal";
|
||
|
|
|
||
|
|
function MyComponent() {
|
||
|
|
const [isOpen, setIsOpen] = useState(false);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<Button onClick={() => setIsOpen(true)}>수주 등록</Button>
|
||
|
|
|
||
|
|
<OrderRegistrationModal
|
||
|
|
open={isOpen}
|
||
|
|
onOpenChange={setIsOpen}
|
||
|
|
onSuccess={() => {
|
||
|
|
console.log("수주 등록 완료!");
|
||
|
|
// 목록 새로고침 등
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 기능
|
||
|
|
|
||
|
|
- **입력 방식 선택**: 거래처 우선, 견적 방식, 단가 방식
|
||
|
|
- **거래처 검색**: 자동완성 드롭다운으로 거래처 검색 및 선택
|
||
|
|
- **품목 관리**: 모달에서 품목 검색 및 추가, 수량/단가 입력, 금액 자동 계산
|
||
|
|
- **전체 금액 표시**: 추가된 품목들의 총 금액 계산
|
||
|
|
- **유효성 검사**: 거래처 및 품목 필수 입력 체크
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. OrderCustomerSearch
|
||
|
|
|
||
|
|
수주 등록 전용 거래처 검색 컴포넌트입니다.
|
||
|
|
|
||
|
|
### 특징
|
||
|
|
|
||
|
|
- `customer_mng` 테이블만 조회 (고정)
|
||
|
|
- 거래처명, 거래처코드, 사업자번호로 검색 (고정)
|
||
|
|
- 추가 정보 표시 (주소, 연락처)
|
||
|
|
|
||
|
|
### Props
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface OrderCustomerSearchProps {
|
||
|
|
/** 현재 선택된 거래처 코드 */
|
||
|
|
value: string;
|
||
|
|
/** 거래처 선택 시 콜백 (거래처 코드, 전체 데이터) */
|
||
|
|
onChange: (customerCode: string | null, fullData?: any) => void;
|
||
|
|
/** 비활성화 여부 */
|
||
|
|
disabled?: boolean;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 사용 예시
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
import { OrderCustomerSearch } from "@/components/order/OrderCustomerSearch";
|
||
|
|
|
||
|
|
function MyForm() {
|
||
|
|
const [customerCode, setCustomerCode] = useState("");
|
||
|
|
const [customerName, setCustomerName] = useState("");
|
||
|
|
|
||
|
|
return (
|
||
|
|
<OrderCustomerSearch
|
||
|
|
value={customerCode}
|
||
|
|
onChange={(code, fullData) => {
|
||
|
|
setCustomerCode(code || "");
|
||
|
|
setCustomerName(fullData?.customer_name || "");
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 고정 설정
|
||
|
|
|
||
|
|
| 설정 | 값 | 설명 |
|
||
|
|
|------|-----|------|
|
||
|
|
| `tableName` | `customer_mng` | 거래처 테이블 |
|
||
|
|
| `displayField` | `customer_name` | 표시 필드 |
|
||
|
|
| `valueField` | `customer_code` | 값 필드 |
|
||
|
|
| `searchFields` | `["customer_name", "customer_code", "business_number"]` | 검색 대상 필드 |
|
||
|
|
| `additionalFields` | `["customer_code", "address", "contact_phone"]` | 추가 표시 필드 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. OrderItemRepeaterTable
|
||
|
|
|
||
|
|
수주 등록 전용 품목 반복 테이블 컴포넌트입니다.
|
||
|
|
|
||
|
|
### 특징
|
||
|
|
|
||
|
|
- `item_info` 테이블만 조회 (고정)
|
||
|
|
- 수주에 필요한 컬럼만 표시 (품번, 품명, 수량, 단가, 금액 등)
|
||
|
|
- 금액 자동 계산 (`수량 * 단가`)
|
||
|
|
|
||
|
|
### Props
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface OrderItemRepeaterTableProps {
|
||
|
|
/** 현재 선택된 품목 목록 */
|
||
|
|
value: any[];
|
||
|
|
/** 품목 목록 변경 시 콜백 */
|
||
|
|
onChange: (items: any[]) => void;
|
||
|
|
/** 비활성화 여부 */
|
||
|
|
disabled?: boolean;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 사용 예시
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
import { OrderItemRepeaterTable } from "@/components/order/OrderItemRepeaterTable";
|
||
|
|
|
||
|
|
function MyForm() {
|
||
|
|
const [items, setItems] = useState([]);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<OrderItemRepeaterTable
|
||
|
|
value={items}
|
||
|
|
onChange={setItems}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 고정 컬럼 설정
|
||
|
|
|
||
|
|
| 필드 | 라벨 | 타입 | 편집 | 필수 | 계산 | 설명 |
|
||
|
|
|------|------|------|------|------|------|------|
|
||
|
|
| `id` | 품번 | text | ❌ | - | - | 품목 ID |
|
||
|
|
| `item_name` | 품명 | text | ❌ | - | - | 품목명 |
|
||
|
|
| `item_number` | 품목번호 | text | ❌ | - | - | 품목 번호 |
|
||
|
|
| `quantity` | 수량 | number | ✅ | ✅ | - | 주문 수량 (기본값: 1) |
|
||
|
|
| `selling_price` | 단가 | number | ✅ | ✅ | - | 판매 단가 |
|
||
|
|
| `amount` | 금액 | number | ❌ | - | ✅ | 자동 계산 (수량 * 단가) |
|
||
|
|
| `delivery_date` | 납품일 | date | ✅ | - | - | 납품 예정일 |
|
||
|
|
| `note` | 비고 | text | ✅ | - | - | 추가 메모 |
|
||
|
|
|
||
|
|
### 계산 규칙
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
amount = quantity * selling_price
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 범용 컴포넌트 vs 전용 컴포넌트
|
||
|
|
|
||
|
|
### 왜 전용 컴포넌트를 만들었나?
|
||
|
|
|
||
|
|
| 항목 | 범용 컴포넌트 | 전용 컴포넌트 |
|
||
|
|
|------|--------------|--------------|
|
||
|
|
| **목적** | 화면 편집기에서 다양한 용도로 사용 | 수주 등록 전용 |
|
||
|
|
| **설정** | ConfigPanel에서 자유롭게 변경 가능 | 하드코딩으로 고정 |
|
||
|
|
| **유연성** | 높음 (모든 테이블/필드 지원) | 낮음 (수주에 최적화) |
|
||
|
|
| **안정성** | 사용자 실수 가능 | 설정 변경 불가로 안전 |
|
||
|
|
| **위치** | `lib/registry/components/` | `components/order/` |
|
||
|
|
|
||
|
|
### 범용 컴포넌트 (화면 편집기용)
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// ❌ 수주 등록에서 사용 금지
|
||
|
|
<AutocompleteSearchInputComponent
|
||
|
|
tableName="???" // ConfigPanel에서 변경 가능
|
||
|
|
displayField="???" // 다른 테이블로 바꿀 수 있음
|
||
|
|
valueField="???" // 필드가 맞지 않으면 에러
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
**문제점:**
|
||
|
|
- 사용자가 `tableName`을 `item_info`로 변경하면 거래처가 아닌 품목이 조회됨
|
||
|
|
- `valueField`를 변경하면 `formData.customerCode`에 잘못된 값 저장
|
||
|
|
- 수주 로직이 깨짐
|
||
|
|
|
||
|
|
### 전용 컴포넌트 (수주 등록용)
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// ✅ 수주 등록에서 사용
|
||
|
|
<OrderCustomerSearch
|
||
|
|
value={customerCode} // 외부에서 제어 가능
|
||
|
|
onChange={handleChange} // 값 변경만 처리
|
||
|
|
// 나머지 설정은 내부에서 고정
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
**장점:**
|
||
|
|
- 설정이 하드코딩되어 있어 변경 불가
|
||
|
|
- 수주 등록 로직에 최적화
|
||
|
|
- 안전하고 예측 가능
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## API 엔드포인트
|
||
|
|
|
||
|
|
### 거래처 검색
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/entity-search/customer_mng
|
||
|
|
Query Parameters:
|
||
|
|
- searchText: 검색어
|
||
|
|
- searchFields: customer_name,customer_code,business_number
|
||
|
|
- page: 페이지 번호
|
||
|
|
- limit: 페이지 크기
|
||
|
|
```
|
||
|
|
|
||
|
|
### 품목 검색
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/entity-search/item_info
|
||
|
|
Query Parameters:
|
||
|
|
- searchText: 검색어
|
||
|
|
- searchFields: item_name,id,item_number
|
||
|
|
- page: 페이지 번호
|
||
|
|
- limit: 페이지 크기
|
||
|
|
```
|
||
|
|
|
||
|
|
### 수주 등록
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /api/orders
|
||
|
|
Body:
|
||
|
|
{
|
||
|
|
inputMode: "customer_first" | "quotation" | "unit_price",
|
||
|
|
customerCode: string,
|
||
|
|
deliveryDate?: string,
|
||
|
|
items: Array<{
|
||
|
|
id: string,
|
||
|
|
item_name: string,
|
||
|
|
quantity: number,
|
||
|
|
selling_price: number,
|
||
|
|
amount: number,
|
||
|
|
delivery_date?: string,
|
||
|
|
note?: string
|
||
|
|
}>,
|
||
|
|
memo?: string
|
||
|
|
}
|
||
|
|
|
||
|
|
Response:
|
||
|
|
{
|
||
|
|
success: boolean,
|
||
|
|
data?: {
|
||
|
|
orderNumber: string,
|
||
|
|
orderId: number
|
||
|
|
},
|
||
|
|
message?: string
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 멀티테넌시 (Multi-Tenancy)
|
||
|
|
|
||
|
|
모든 API 호출은 자동으로 `company_code` 필터링이 적용됩니다.
|
||
|
|
|
||
|
|
- 거래처 검색: 현재 로그인한 사용자의 회사에 속한 거래처만 조회
|
||
|
|
- 품목 검색: 현재 로그인한 사용자의 회사에 속한 품목만 조회
|
||
|
|
- 수주 등록: 자동으로 현재 사용자의 `company_code` 추가
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 트러블슈팅
|
||
|
|
|
||
|
|
### 1. 거래처가 검색되지 않음
|
||
|
|
|
||
|
|
**원인**: `customer_mng` 테이블에 데이터가 없거나 `company_code`가 다름
|
||
|
|
|
||
|
|
**해결**:
|
||
|
|
```sql
|
||
|
|
-- 거래처 데이터 확인
|
||
|
|
SELECT * FROM customer_mng WHERE company_code = 'YOUR_COMPANY_CODE';
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 품목이 검색되지 않음
|
||
|
|
|
||
|
|
**원인**: `item_info` 테이블에 데이터가 없거나 `company_code`가 다름
|
||
|
|
|
||
|
|
**해결**:
|
||
|
|
```sql
|
||
|
|
-- 품목 데이터 확인
|
||
|
|
SELECT * FROM item_info WHERE company_code = 'YOUR_COMPANY_CODE';
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. 수주 등록 실패
|
||
|
|
|
||
|
|
**원인**: 필수 필드 누락 또는 백엔드 API 오류
|
||
|
|
|
||
|
|
**해결**:
|
||
|
|
1. 브라우저 개발자 도구 콘솔 확인
|
||
|
|
2. 네트워크 탭에서 API 응답 확인
|
||
|
|
3. 백엔드 로그 확인
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 개발 참고 사항
|
||
|
|
|
||
|
|
### 새로운 전용 컴포넌트 추가 시
|
||
|
|
|
||
|
|
1. **범용 컴포넌트 활용**: 기존 범용 컴포넌트를 래핑
|
||
|
|
2. **설정 고정**: 비즈니스 로직에 필요한 설정을 하드코딩
|
||
|
|
3. **Props 최소화**: 외부에서 제어 가능한 최소한의 prop만 노출
|
||
|
|
4. **문서 작성**: README에 사용법 및 고정 설정 명시
|
||
|
|
|
||
|
|
### 예시: 견적 등록 전용 컴포넌트
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// QuotationCustomerSearch.tsx
|
||
|
|
export function QuotationCustomerSearch({ value, onChange }: Props) {
|
||
|
|
return (
|
||
|
|
<AutocompleteSearchInputComponent
|
||
|
|
tableName="customer_mng" // 고정
|
||
|
|
displayField="customer_name" // 고정
|
||
|
|
valueField="customer_code" // 고정
|
||
|
|
value={value}
|
||
|
|
onChange={onChange}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 관련 파일
|
||
|
|
|
||
|
|
- 범용 컴포넌트:
|
||
|
|
- `lib/registry/components/autocomplete-search-input/`
|
||
|
|
- `lib/registry/components/entity-search-input/`
|
||
|
|
- `lib/registry/components/modal-repeater-table/`
|
||
|
|
|
||
|
|
- 백엔드 API:
|
||
|
|
- `backend-node/src/controllers/entitySearchController.ts`
|
||
|
|
- `backend-node/src/controllers/orderController.ts`
|
||
|
|
|
||
|
|
- 계획서:
|
||
|
|
- `수주등록_화면_개발_계획서.md`
|
||
|
|
|