ERP-node/frontend/components/order
kjs 9e956999c5 모달 크기 고정 2025-12-05 10:46:10 +09:00
..
OrderCustomerSearch.tsx feat: 수주등록 모달 및 범용 컴포넌트 개발 2025-11-14 14:43:53 +09:00
OrderItemRepeaterTable.tsx feat: 수주일(order_date) 일괄 적용 기능 구현 2025-11-27 10:33:54 +09:00
OrderRegistrationModal.tsx 모달 크기 고정 2025-12-05 10:46:10 +09:00
README.md feat: 수주등록 모달 및 범용 컴포넌트 개발 2025-11-14 14:43:53 +09:00
orderConstants.ts feat: modal-repeater-table 배열 데이터 저장 기능 구현 2025-11-21 10:12:29 +09:00

README.md

수주 등록 컴포넌트

개요

수주 등록 기능을 위한 전용 컴포넌트들입니다. 이 컴포넌트들은 범용 컴포넌트를 래핑하여 수주 등록에 최적화된 고정 설정을 제공합니다.

컴포넌트 구조

frontend/components/order/
├── OrderRegistrationModal.tsx      # 수주 등록 메인 모달
├── OrderCustomerSearch.tsx         # 거래처 검색 (전용)
├── OrderItemRepeaterTable.tsx      # 품목 반복 테이블 (전용)
└── README.md                        # 문서 (현재 파일)

1. OrderRegistrationModal

수주 등록 메인 모달 컴포넌트입니다.

Props

interface OrderRegistrationModalProps {
  /** 모달 열림/닫힘 상태 */
  open: boolean;
  /** 모달 상태 변경 핸들러 */
  onOpenChange: (open: boolean) => void;
  /** 수주 등록 성공 시 콜백 */
  onSuccess?: () => void;
}

사용 예시

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

interface OrderCustomerSearchProps {
  /** 현재 선택된 거래처 코드 */
  value: string;
  /** 거래처 선택 시 콜백 (거래처 코드, 전체 데이터) */
  onChange: (customerCode: string | null, fullData?: any) => void;
  /** 비활성화 여부 */
  disabled?: boolean;
}

사용 예시

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

interface OrderItemRepeaterTableProps {
  /** 현재 선택된 품목 목록 */
  value: any[];
  /** 품목 목록 변경 시 콜백 */
  onChange: (items: any[]) => void;
  /** 비활성화 여부 */
  disabled?: boolean;
}

사용 예시

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 - - 추가 메모

계산 규칙

amount = quantity * selling_price

범용 컴포넌트 vs 전용 컴포넌트

왜 전용 컴포넌트를 만들었나?

항목 범용 컴포넌트 전용 컴포넌트
목적 화면 편집기에서 다양한 용도로 사용 수주 등록 전용
설정 ConfigPanel에서 자유롭게 변경 가능 하드코딩으로 고정
유연성 높음 (모든 테이블/필드 지원) 낮음 (수주에 최적화)
안정성 사용자 실수 가능 설정 변경 불가로 안전
위치 lib/registry/components/ components/order/

범용 컴포넌트 (화면 편집기용)

// ❌ 수주 등록에서 사용 금지
<AutocompleteSearchInputComponent
  tableName="???"  // ConfigPanel에서 변경 가능
  displayField="???"  // 다른 테이블로 바꿀  있음
  valueField="???"  // 필드가 맞지 않으면 에러
/>

문제점:

  • 사용자가 tableNameitem_info로 변경하면 거래처가 아닌 품목이 조회됨
  • valueField를 변경하면 formData.customerCode에 잘못된 값 저장
  • 수주 로직이 깨짐

전용 컴포넌트 (수주 등록용)

// ✅ 수주 등록에서 사용
<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가 다름

해결:

-- 거래처 데이터 확인
SELECT * FROM customer_mng WHERE company_code = 'YOUR_COMPANY_CODE';

2. 품목이 검색되지 않음

원인: item_info 테이블에 데이터가 없거나 company_code가 다름

해결:

-- 품목 데이터 확인
SELECT * FROM item_info WHERE company_code = 'YOUR_COMPANY_CODE';

3. 수주 등록 실패

원인: 필수 필드 누락 또는 백엔드 API 오류

해결:

  1. 브라우저 개발자 도구 콘솔 확인
  2. 네트워크 탭에서 API 응답 확인
  3. 백엔드 로그 확인

개발 참고 사항

새로운 전용 컴포넌트 추가 시

  1. 범용 컴포넌트 활용: 기존 범용 컴포넌트를 래핑
  2. 설정 고정: 비즈니스 로직에 필요한 설정을 하드코딩
  3. Props 최소화: 외부에서 제어 가능한 최소한의 prop만 노출
  4. 문서 작성: README에 사용법 및 고정 설정 명시

예시: 견적 등록 전용 컴포넌트

// 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