66e2a63dfa
- G1: salesOrderMgmtService.updateStatus 트랜잭션화 + project_mgmt 자동생성 (project_no 채번/Machine 분기/contract_item 라인별 INSERT)
- 수주확정 다이얼로그(상태 select 팝업) + 수주취소 다이얼로그(라인별 cancel_qty)·POST /sales/order-mgmt/:id/cancel-qty 신설
- 견적요청등록 폼: estimate_template 분리, 헤더 8(주문유형/국내해외/고객사/유무상/접수일/견적환종/견적환율/결재여부) + 라인 8(제품구분/품번/품명/S/N/견적수량/요청납기/반납사유/고객요청사항) wace 운영 화면과 1:1
- S/N 관리 다이얼로그(테이블+연속번호생성), PartSelect/CommCodeSelect/CustomerSelect 셀렉트박스 + ✕(선택해제), 수주확정된 행 라인 추가/삭제 차단
- DataGrid 체크박스 모드 (영업번호 No → 체크박스, 행 어디 클릭이나 단일 선택)
- 식별자 정합성: contract_mgmt.customer_objid를 customer_mng.customer_code 기반(C_xxxx)으로 통일, contract_no 채번 {YY}C-{NNNN} 운영 패턴 일치, contract_item.quantity ::integer 캐스트
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
2.9 KiB
TypeScript
99 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect";
|
|
import { apiClient } from "@/lib/api/client";
|
|
|
|
/**
|
|
* 품번/품명 자동완성 셀렉트
|
|
*
|
|
* wace_plm orderMgmtList의 select2-part AJAX 패턴을 단순화한 형태.
|
|
* - item_info 전체를 한 번 캐시 (id 기준 단일 소스)
|
|
* - mode='partNo': 라벨로 item_number 표시
|
|
* - mode='partName': 라벨로 item_name 표시
|
|
* - 선택값(value)은 양쪽 모두 item_info.id (= part_objid)
|
|
*/
|
|
|
|
interface PartRow {
|
|
id: string;
|
|
item_number?: string;
|
|
item_name?: string;
|
|
}
|
|
|
|
interface PartSelectProps {
|
|
mode: "partNo" | "partName";
|
|
/** item_info.id (part_objid) */
|
|
value: string;
|
|
/** 옵션 선택 시 part_objid + (선택사항) 마스터 정보(item_number/item_name) 전달 */
|
|
onValueChange: (partObjId: string, row?: { item_number?: string; item_name?: string }) => void;
|
|
placeholder?: string;
|
|
disabled?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
let cachedRows: PartRow[] | null = null;
|
|
let inflight: Promise<PartRow[]> | null = null;
|
|
|
|
const fetchParts = async (): Promise<PartRow[]> => {
|
|
if (cachedRows) return cachedRows;
|
|
if (inflight) return inflight;
|
|
inflight = (async () => {
|
|
// 영업관리 4개 메뉴 공통 endpoint — wace 이식 8179건 + COMPANY_16 데이터.
|
|
const res = await apiClient.get("/sales/parts");
|
|
const rows = (res.data?.data ?? []) as any[];
|
|
cachedRows = rows
|
|
.filter((r) => r.id != null)
|
|
.map((r) => ({
|
|
id: String(r.id),
|
|
item_number: r.item_number ?? "",
|
|
item_name: r.item_name ?? "",
|
|
}));
|
|
return cachedRows!;
|
|
})();
|
|
try {
|
|
return await inflight;
|
|
} finally {
|
|
inflight = null;
|
|
}
|
|
};
|
|
|
|
const toOptions = (rows: PartRow[], mode: PartSelectProps["mode"]): SmartSelectOption[] =>
|
|
rows
|
|
.filter((r) => mode === "partNo" ? r.item_number : r.item_name)
|
|
.map((r) => ({
|
|
code: r.id,
|
|
label: String(mode === "partNo" ? r.item_number : r.item_name),
|
|
}));
|
|
|
|
export function PartSelect({
|
|
mode, value, onValueChange,
|
|
placeholder = mode === "partNo" ? "품번 선택" : "품명 선택",
|
|
disabled, className,
|
|
}: PartSelectProps) {
|
|
const [options, setOptions] = useState<SmartSelectOption[]>(
|
|
cachedRows ? toOptions(cachedRows, mode) : [],
|
|
);
|
|
|
|
useEffect(() => {
|
|
let alive = true;
|
|
fetchParts()
|
|
.then((rows) => { if (alive) setOptions(toOptions(rows, mode)); })
|
|
.catch(() => {});
|
|
return () => { alive = false; };
|
|
}, [mode]);
|
|
|
|
return (
|
|
<SmartSelect
|
|
options={options}
|
|
value={value}
|
|
onValueChange={(v) => {
|
|
const row = cachedRows?.find((r) => r.id === v);
|
|
onValueChange(v, row ? { item_number: row.item_number, item_name: row.item_name } : undefined);
|
|
}}
|
|
placeholder={placeholder}
|
|
disabled={disabled}
|
|
className={className}
|
|
/>
|
|
);
|
|
}
|