diff --git a/frontend/app/(main)/COMPANY_16/logistics/info/page.tsx b/frontend/app/(main)/COMPANY_16/logistics/info/page.tsx index 29be86d7..1e60f516 100644 --- a/frontend/app/(main)/COMPANY_16/logistics/info/page.tsx +++ b/frontend/app/(main)/COMPANY_16/logistics/info/page.tsx @@ -50,6 +50,7 @@ import { apiClient } from "@/lib/api/client"; import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { exportToExcel } from "@/lib/utils/excelExport"; +import { previewNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule"; import { useTableSettings } from "@/hooks/useTableSettings"; import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; @@ -241,6 +242,13 @@ function flattenCategories(items: any[]): { value: string; label: string }[] { return result; } +// 채번 대상 필드 매핑: tableName → 코드 필드 key +const NUMBERING_FIELD_MAP: Record = { + carrier_mng: "carrier_code", + delivery_route_mng: "route_code", + carrier_vehicle_mng: "vehicle_code", +}; + // ========== 메인 컴포넌트 ========== export default function LogisticsInfoPage() { const { user } = useAuth(); @@ -277,6 +285,10 @@ export default function LogisticsInfoPage() { const [editId, setEditId] = useState(null); const [formData, setFormData] = useState>({}); + // 채번 시스템 + const [numberingRuleId, setNumberingRuleId] = useState(null); + const [previewCode, setPreviewCode] = useState(null); + // 테이블 설정 (탭별) const tsCarrier = useTableSettings("c16-logistics-carrier", TAB_CONFIGS[0].tableName, TAB_CONFIGS[0].columns); const tsCost = useTableSettings("c16-logistics-cost", TAB_CONFIGS[1].tableName, TAB_CONFIGS[1].columns); @@ -407,12 +419,37 @@ export default function LogisticsInfoPage() { }, []); // 등록 모달 열기 - const handleOpenAdd = useCallback(() => { + const handleOpenAdd = useCallback(async () => { setEditMode(false); setEditId(null); setFormData({}); + setPreviewCode(null); + setNumberingRuleId(null); setFormOpen(true); - }, []); + + // 현재 탭의 채번 규칙 조회 + const config = TAB_CONFIGS.find((c) => c.key === activeTab); + if (!config) return; + const codeField = NUMBERING_FIELD_MAP[config.tableName]; + if (!codeField) return; // 채번 대상이 아닌 탭 + + try { + const ruleRes = await apiClient.get( + `/numbering-rules/by-column/${config.tableName}/${codeField}` + ); + const ruleData = ruleRes.data; + if (ruleData?.success && ruleData?.data?.ruleId) { + const ruleId = ruleData.data.ruleId; + setNumberingRuleId(ruleId); + const previewRes = await previewNumberingCode(ruleId); + if (previewRes.success && previewRes.data?.generatedCode) { + setPreviewCode(previewRes.data.generatedCode); + } + } + } catch { + // 채번 규칙 없으면 무시 — 사용자가 직접 입력 + } + }, [activeTab]); // 수정 모달 열기 const handleOpenEdit = useCallback((row: any) => { @@ -427,8 +464,10 @@ export default function LogisticsInfoPage() { const config = TAB_CONFIGS.find((c) => c.key === activeTab); if (!config) return; - // 필수값 검증 + // 필수값 검증 (등록 모드에서 채번 대상 코드 필드는 자동 할당이므로 스킵) + const numberingCodeField = NUMBERING_FIELD_MAP[config.tableName]; for (const field of config.formFields) { + if (!editMode && numberingRuleId && field.key === numberingCodeField) continue; if (field.required && !formData[field.key]?.toString().trim()) { toast.error(`${field.label}은(는) 필수 입력이에요.`); return; @@ -449,6 +488,18 @@ export default function LogisticsInfoPage() { }); toast.success("수정이 완료되었어요."); } else { + // 채번 규칙이 있으면 allocate로 실제 코드 할당 + const codeField = NUMBERING_FIELD_MAP[config.tableName]; + if (codeField && numberingRuleId) { + const allocRes = await allocateNumberingCode(numberingRuleId); + if (allocRes.success && allocRes.data?.generatedCode) { + saveData[codeField] = allocRes.data.generatedCode; + } else { + toast.error("채번 코드 할당에 실패했습니다."); + return; + } + } + await apiClient.post( `/table-management/tables/${config.tableName}/add`, { id: crypto.randomUUID(), ...saveData } @@ -464,7 +515,7 @@ export default function LogisticsInfoPage() { } catch (err: any) { toast.error(err?.response?.data?.message || "저장에 실패했어요."); } - }, [activeTab, editMode, editId, formData, fetchTabData, loadReferences]); + }, [activeTab, editMode, editId, formData, fetchTabData, loadReferences, numberingRuleId]); // 삭제 const handleDelete = useCallback(async () => { @@ -543,6 +594,9 @@ export default function LogisticsInfoPage() { const renderFormField = useCallback( (field: FormFieldDef) => { const value = formData[field.key] ?? ""; + // 현재 탭의 채번 대상 코드 필드인지 확인 + const numberingCodeField = NUMBERING_FIELD_MAP[activeConfig.tableName]; + const isNumberingTarget = !editMode && numberingRuleId && field.key === numberingCodeField; // 수정 모드에서 코드/번호 필드는 읽기전용 const isCodeField = editMode && @@ -551,6 +605,17 @@ export default function LogisticsInfoPage() { switch (field.type) { case "text": + // 등록 모드 + 채번 대상 필드: readOnly로 미리보기 코드 표시 + if (isNumberingTarget) { + return ( + + ); + } return ( >({}); const [locationSaving, setLocationSaving] = useState(false); + // 위치코드 자동 채번 + const [locNumberingRuleId, setLocNumberingRuleId] = useState(null); + const [locPreviewCode, setLocPreviewCode] = useState(null); + const [whNumberingRuleId, setWhNumberingRuleId] = useState(null); + const [whPreviewCode, setWhPreviewCode] = useState(null); + // 모달: 랙 구조 일괄 등록 const [rackModalOpen, setRackModalOpen] = useState(false); const [rackFloor, setRackFloor] = useState(""); @@ -314,10 +321,24 @@ export default function WarehouseManagementPage() { // ─── 창고 CRUD ─── - const openWarehouseCreateModal = () => { + const openWarehouseCreateModal = async () => { setWarehouseEditMode(false); setWarehouseForm({}); + setWhNumberingRuleId(null); + setWhPreviewCode(null); setWarehouseModalOpen(true); + try { + const ruleRes = await apiClient.get(`/numbering-rules/by-column/warehouse_info/warehouse_code`); + const ruleData = ruleRes.data; + if (ruleData?.success && ruleData?.data?.ruleId) { + const ruleId = ruleData.data.ruleId; + setWhNumberingRuleId(ruleId); + const previewRes = await previewNumberingCode(ruleId); + if (previewRes.success && previewRes.data?.generatedCode) { + setWhPreviewCode(previewRes.data.generatedCode); + } + } + } catch { /* 채번 규칙 없으면 무시 */ } }; const openWarehouseEditModal = (row: any) => { @@ -327,7 +348,7 @@ export default function WarehouseManagementPage() { }; const handleWarehouseSave = async () => { - if (!warehouseForm.warehouse_code?.trim()) { + if (!whNumberingRuleId && !warehouseForm.warehouse_code?.trim()) { toast.error("창고코드를 입력해주세요"); return; } @@ -337,8 +358,19 @@ export default function WarehouseManagementPage() { } setWarehouseSaving(true); try { + let finalWarehouseCode = warehouseForm.warehouse_code?.trim() || ""; + if (!warehouseEditMode && whNumberingRuleId) { + const allocRes = await allocateNumberingCode(whNumberingRuleId); + if (allocRes.success && allocRes.data?.generatedCode) { + finalWarehouseCode = allocRes.data.generatedCode; + } else { + toast.error("채번 코드 할당에 실패했습니다."); + setWarehouseSaving(false); + return; + } + } const fields = { - warehouse_code: warehouseForm.warehouse_code?.trim(), + warehouse_code: finalWarehouseCode, warehouse_name: warehouseForm.warehouse_name?.trim(), warehouse_type: warehouseForm.warehouse_type || "", manager: warehouseForm.manager || "", @@ -407,10 +439,24 @@ export default function WarehouseManagementPage() { // ─── 위치 CRUD ─── - const openLocationCreateModal = () => { + const openLocationCreateModal = async () => { setLocationEditMode(false); setLocationForm({ warehouse_code: selectedWarehouse?.warehouse_code || "" }); + setLocNumberingRuleId(null); + setLocPreviewCode(null); setLocationModalOpen(true); + try { + const ruleRes = await apiClient.get(`/numbering-rules/by-column/warehouse_location/location_code`); + const ruleData = ruleRes.data; + if (ruleData?.success && ruleData?.data?.ruleId) { + const ruleId = ruleData.data.ruleId; + setLocNumberingRuleId(ruleId); + const previewRes = await previewNumberingCode(ruleId); + if (previewRes.success && previewRes.data?.generatedCode) { + setLocPreviewCode(previewRes.data.generatedCode); + } + } + } catch { /* 채번 규칙 없으면 무시 */ } }; const openLocationEditModal = (row: any) => { @@ -420,7 +466,7 @@ export default function WarehouseManagementPage() { }; const handleLocationSave = async () => { - if (!locationForm.location_code?.trim()) { + if (!locNumberingRuleId && !locationForm.location_code?.trim()) { toast.error("위치코드를 입력해주세요"); return; } @@ -430,9 +476,21 @@ export default function WarehouseManagementPage() { } setLocationSaving(true); try { + let finalLocationCode = locationForm.location_code?.trim() || ""; + if (!locationEditMode && locNumberingRuleId) { + const allocRes = await allocateNumberingCode(locNumberingRuleId); + if (allocRes.success && allocRes.data?.generatedCode) { + finalLocationCode = allocRes.data.generatedCode; + } else { + toast.error("채번 코드 할당에 실패했습니다."); + setLocationSaving(false); + return; + } + } + const fields = { warehouse_code: locationForm.warehouse_code || selectedWarehouse?.warehouse_code || "", - location_code: locationForm.location_code?.trim(), + location_code: finalLocationCode, location_name: locationForm.location_name?.trim(), floor: locationForm.floor || "", zone: locationForm.zone || "", @@ -959,12 +1017,13 @@ export default function WarehouseManagementPage() { 창고코드 * setWarehouseForm((prev) => ({ ...prev, warehouse_code: e.target.value })) } - placeholder="창고코드를 입력해주세요" + placeholder={warehouseEditMode ? "" : (whNumberingRuleId ? "채번 조회 중..." : "자동 생성돼요")} disabled={warehouseEditMode} + readOnly={!warehouseEditMode && !!whNumberingRuleId} /> {/* 창고명 */} @@ -1097,12 +1156,13 @@ export default function WarehouseManagementPage() { 위치코드 * setLocationForm((prev) => ({ ...prev, location_code: e.target.value })) } - placeholder="위치코드를 입력해주세요" + placeholder={locationEditMode ? "" : (locNumberingRuleId ? "채번 조회 중..." : "자동 생성돼요")} disabled={locationEditMode} + readOnly={!locationEditMode && !!locNumberingRuleId} /> {/* 위치명 */} diff --git a/frontend/app/(main)/COMPANY_16/mold/info/page.tsx b/frontend/app/(main)/COMPANY_16/mold/info/page.tsx index 332b3fbc..dcd92eac 100644 --- a/frontend/app/(main)/COMPANY_16/mold/info/page.tsx +++ b/frontend/app/(main)/COMPANY_16/mold/info/page.tsx @@ -25,6 +25,7 @@ import { } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; +import { previewNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule"; import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { useConfirmDialog } from "@/components/common/ConfirmDialog"; @@ -98,6 +99,8 @@ export default function MoldInfoPage() { const [moldModalOpen, setMoldModalOpen] = useState(false); const [moldEditMode, setMoldEditMode] = useState(false); const [moldForm, setMoldForm] = useState>({}); + const [numberingRuleId, setNumberingRuleId] = useState(null); + const [previewCode, setPreviewCode] = useState(null); const [saving, setSaving] = useState(false); const [moldImagePreview, setMoldImagePreview] = useState(null); const moldImageRef = React.useRef(null); @@ -204,11 +207,29 @@ export default function MoldInfoPage() { }, [rightTab, selectedMoldCode, fetchSerials, fetchInspections, fetchParts]); // ─── 금형 CRUD ─── - const handleOpenRegister = () => { + const handleOpenRegister = async () => { setMoldEditMode(false); setMoldForm({}); setMoldImagePreview(null); + setNumberingRuleId(null); + setPreviewCode(null); setMoldModalOpen(true); + + // 채번 규칙 조회 (mold_mng.mold_code) + try { + const ruleRes = await apiClient.get(`/numbering-rules/by-column/mold_mng/mold_code`); + const ruleData = ruleRes.data; + if (ruleData?.success && ruleData?.data?.ruleId) { + const ruleId = ruleData.data.ruleId; + setNumberingRuleId(ruleId); + const previewRes = await previewNumberingCode(ruleId); + if (previewRes.success && previewRes.data?.generatedCode) { + setPreviewCode(previewRes.data.generatedCode); + } + } + } catch { + // 채번 규칙 없으면 무시 — 수동 입력 모드 + } }; const handleOpenEdit = () => { @@ -232,17 +253,37 @@ export default function MoldInfoPage() { }; const handleSaveMold = async () => { - if (!moldForm.mold_code || !moldForm.mold_name) { + // 등록 모드에서 채번이 없으면 수동 입력 필수 + if (!moldEditMode && !numberingRuleId && !moldForm.mold_code) { toast.error("금형코드와 금형명은 필수예요."); return; } + if (!moldForm.mold_name) { + toast.error("금형명은 필수예요."); + return; + } + if (moldEditMode && !moldForm.mold_code) { + toast.error("금형코드가 없어요."); + return; + } setSaving(true); try { if (moldEditMode) { await apiClient.put(`${API}/${moldForm.mold_code}`, moldForm); toast.success("금형이 수정되었어요."); } else { - await apiClient.post(API, moldForm); + // 채번 규칙이 있으면 allocate로 실제 코드 할당 + let saveData = { ...moldForm }; + if (numberingRuleId) { + const allocRes = await allocateNumberingCode(numberingRuleId); + if (allocRes.success && allocRes.data?.generatedCode) { + saveData.mold_code = allocRes.data.generatedCode; + } else { + toast.error("채번 코드 할당에 실패했어요."); + return; + } + } + await apiClient.post(API, saveData); toast.success("금형이 등록되었어요."); } setMoldModalOpen(false); @@ -980,10 +1021,14 @@ export default function MoldInfoPage() { setMoldForm({ ...moldForm, mold_code: e.target.value })} - readOnly={moldEditMode} - placeholder="금형코드를 입력해주세요" + value={moldEditMode ? (moldForm.mold_code || "") : (numberingRuleId ? (previewCode || "") : (moldForm.mold_code || ""))} + onChange={(e) => { + if (!moldEditMode && !numberingRuleId) { + setMoldForm({ ...moldForm, mold_code: e.target.value }); + } + }} + readOnly={moldEditMode || !!numberingRuleId} + placeholder={moldEditMode ? "" : (numberingRuleId ? (previewCode ? "" : "자동 생성 중...") : "금형코드를 입력해주세요")} />
diff --git a/frontend/app/(main)/COMPANY_16/outsourcing/subcontractor/page.tsx b/frontend/app/(main)/COMPANY_16/outsourcing/subcontractor/page.tsx index 82b9c7d6..24c3caee 100644 --- a/frontend/app/(main)/COMPANY_16/outsourcing/subcontractor/page.tsx +++ b/frontend/app/(main)/COMPANY_16/outsourcing/subcontractor/page.tsx @@ -28,6 +28,7 @@ import { MultiTableExcelUploadModal } from "@/components/common/MultiTableExcelU import { autoDetectMultiTableConfig, TableChainConfig } from "@/lib/api/multiTableExcel"; import { exportToExcel } from "@/lib/utils/excelExport"; import { validateField, validateForm, formatField } from "@/lib/utils/validation"; +import { previewNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule"; import { useTableSettings } from "@/hooks/useTableSettings"; import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; @@ -84,6 +85,10 @@ export default function SubcontractorManagementPage() { const [formErrors, setFormErrors] = useState>({}); const [saving, setSaving] = useState(false); + // 채번 시스템 + const [numberingRuleId, setNumberingRuleId] = useState(null); + const [previewCode, setPreviewCode] = useState(null); + // 품목 추가 모달 (1단계: 검색/선택) const [itemSelectOpen, setItemSelectOpen] = useState(false); const [itemSearchKeyword, setItemSearchKeyword] = useState(""); @@ -282,11 +287,29 @@ export default function SubcontractorManagementPage() { }, [selectedSubcontractor?.subcontractor_code, priceCategoryOptions]); // 외주업체 등록 모달 열기 - const openSubcontractorRegister = () => { + const openSubcontractorRegister = async () => { setSubcontractorForm({}); setFormErrors({}); setSubcontractorEditMode(false); + setNumberingRuleId(null); + setPreviewCode(null); setSubcontractorModalOpen(true); + + // 채번 규칙 조회 (subcontractor_mng.subcontractor_code) + try { + const ruleRes = await apiClient.get(`/numbering-rules/by-column/subcontractor_mng/subcontractor_code`); + const ruleData = ruleRes.data; + if (ruleData?.success && ruleData?.data?.ruleId) { + const ruleId = ruleData.data.ruleId; + setNumberingRuleId(ruleId); + const previewRes = await previewNumberingCode(ruleId); + if (previewRes.success && previewRes.data?.generatedCode) { + setPreviewCode(previewRes.data.generatedCode); + } + } + } catch { + // 채번 규칙 없으면 무시 + } }; const openSubcontractorEdit = () => { @@ -326,7 +349,19 @@ export default function SubcontractorManagementPage() { }); toast.success("수정되었어요"); } else { - await apiClient.post(`/table-management/tables/${SUBCONTRACTOR_TABLE}/add`, { id: crypto.randomUUID(), ...fields }); + // 채번 규칙이 있으면 allocate로 실제 코드 할당 + let allocatedCode: string | undefined; + if (numberingRuleId) { + const allocRes = await allocateNumberingCode(numberingRuleId); + if (allocRes.success && allocRes.data?.generatedCode) { + allocatedCode = allocRes.data.generatedCode; + } else { + toast.error("채번 코드 할당에 실패했습니다."); + return; + } + } + const saveFields = allocatedCode ? { ...fields, subcontractor_code: allocatedCode } : fields; + await apiClient.post(`/table-management/tables/${SUBCONTRACTOR_TABLE}/add`, { id: crypto.randomUUID(), ...saveFields }); toast.success("등록되었어요"); } setSubcontractorModalOpen(false); @@ -1003,10 +1038,15 @@ export default function SubcontractorManagementPage() {
setSubcontractorForm((p) => ({ ...p, subcontractor_code: e.target.value }))} - placeholder="외주업체 코드" + value={subcontractorEditMode ? (subcontractorForm.subcontractor_code || "") : (previewCode || subcontractorForm.subcontractor_code || "")} + onChange={(e) => { + if (!subcontractorEditMode && !numberingRuleId) { + setSubcontractorForm((p) => ({ ...p, subcontractor_code: e.target.value })); + } + }} + placeholder={numberingRuleId && !subcontractorEditMode ? "자동 생성돼요" : "외주업체 코드"} className="h-9 text-sm" + readOnly={!!numberingRuleId && !subcontractorEditMode} disabled={subcontractorEditMode} />
diff --git a/frontend/app/(main)/COMPANY_16/quality/inspection/page.tsx b/frontend/app/(main)/COMPANY_16/quality/inspection/page.tsx index 56226c40..41eab9c1 100644 --- a/frontend/app/(main)/COMPANY_16/quality/inspection/page.tsx +++ b/frontend/app/(main)/COMPANY_16/quality/inspection/page.tsx @@ -33,6 +33,7 @@ import { import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; +import { previewNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule"; import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { useConfirmDialog } from "@/components/common/ConfirmDialog"; @@ -106,6 +107,10 @@ export default function InspectionManagementPage() { const [eqSaving, setEqSaving] = useState(false); const [eqKeyword, setEqKeyword] = useState(""); + /* ───── 채번 ───── */ + const [numberingRuleId, setNumberingRuleId] = useState(null); + const [previewCode, setPreviewCode] = useState(null); + /* ───── 카테고리 옵션 ───── */ const [catOptions, setCatOptions] = useState>({}); const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]); @@ -266,10 +271,24 @@ export default function InspectionManagementPage() { : equipments; /* ═══════════════════ 검사기준 CRUD ═══════════════════ */ - const openInspCreate = () => { + const openInspCreate = async () => { setInspForm({}); setInspEditMode(false); + setNumberingRuleId(null); + setPreviewCode(null); setInspModalOpen(true); + try { + const ruleRes = await apiClient.get(`/numbering-rules/by-column/${INSPECTION_TABLE}/inspection_code`); + const ruleData = ruleRes.data; + if (ruleData?.success && ruleData?.data?.ruleId) { + const ruleId = ruleData.data.ruleId; + setNumberingRuleId(ruleId); + const prev = await previewNumberingCode(ruleId); + if (prev.success && prev.data?.generatedCode) { + setPreviewCode(prev.data.generatedCode); + } + } + } catch { /* 채번 규칙 없으면 무시 */ } }; const openInspEdit = (row: any) => { setInspForm({ ...row }); @@ -277,7 +296,7 @@ export default function InspectionManagementPage() { setInspModalOpen(true); }; const saveInspection = async () => { - if (!inspForm.inspection_code) { + if (!numberingRuleId && !inspForm.inspection_code) { toast.error("검사코드는 필수예요"); return; } @@ -299,6 +318,17 @@ export default function InspectionManagementPage() { } setInspSaving(true); try { + let finalCode = inspForm.inspection_code || ""; + if (!inspEditMode && numberingRuleId) { + const allocRes = await allocateNumberingCode(numberingRuleId); + if (allocRes.success && allocRes.data?.generatedCode) { + finalCode = allocRes.data.generatedCode; + } else { + toast.error("채번 코드 할당에 실패했습니다."); + setInspSaving(false); + return; + } + } if (inspEditMode) { await apiClient.put(`/table-management/tables/${INSPECTION_TABLE}/edit`, { originalData: { id: inspForm.id }, @@ -309,6 +339,7 @@ export default function InspectionManagementPage() { await apiClient.post(`/table-management/tables/${INSPECTION_TABLE}/add`, { id: crypto.randomUUID(), ...inspForm, + inspection_code: finalCode, }); toast.success("검사기준을 등록했어요"); } @@ -342,10 +373,24 @@ export default function InspectionManagementPage() { }; /* ═══════════════════ 불량관리 CRUD ═══════════════════ */ - const openDefCreate = () => { + const openDefCreate = async () => { setDefForm({}); setDefEditMode(false); + setNumberingRuleId(null); + setPreviewCode(null); setDefModalOpen(true); + try { + const ruleRes = await apiClient.get(`/numbering-rules/by-column/${DEFECT_TABLE}/defect_code`); + const ruleData = ruleRes.data; + if (ruleData?.success && ruleData?.data?.ruleId) { + const ruleId = ruleData.data.ruleId; + setNumberingRuleId(ruleId); + const prev = await previewNumberingCode(ruleId); + if (prev.success && prev.data?.generatedCode) { + setPreviewCode(prev.data.generatedCode); + } + } + } catch { /* 채번 규칙 없으면 무시 */ } }; const openDefEdit = (row: any) => { setDefForm({ ...row }); @@ -353,7 +398,7 @@ export default function InspectionManagementPage() { setDefModalOpen(true); }; const saveDefect = async () => { - if (!defForm.defect_code) { + if (!numberingRuleId && !defForm.defect_code) { toast.error("불량코드는 필수예요"); return; } @@ -379,6 +424,17 @@ export default function InspectionManagementPage() { } setDefSaving(true); try { + let finalCode = defForm.defect_code || ""; + if (!defEditMode && numberingRuleId) { + const allocRes = await allocateNumberingCode(numberingRuleId); + if (allocRes.success && allocRes.data?.generatedCode) { + finalCode = allocRes.data.generatedCode; + } else { + toast.error("채번 코드 할당에 실패했습니다."); + setDefSaving(false); + return; + } + } if (defEditMode) { await apiClient.put(`/table-management/tables/${DEFECT_TABLE}/edit`, { originalData: { id: defForm.id }, @@ -386,7 +442,7 @@ export default function InspectionManagementPage() { }); toast.success("불량유형을 수정했어요"); } else { - await apiClient.post(`/table-management/tables/${DEFECT_TABLE}/add`, { id: crypto.randomUUID(), ...defForm }); + await apiClient.post(`/table-management/tables/${DEFECT_TABLE}/add`, { id: crypto.randomUUID(), ...defForm, defect_code: finalCode }); toast.success("불량유형을 등록했어요"); } setDefModalOpen(false); @@ -419,20 +475,45 @@ export default function InspectionManagementPage() { }; /* ═══════════════════ 검사장비 CRUD ═══════════════════ */ - const openEqCreate = () => { - const maxNum = - equipments - .map((e: any) => e.equipment_code || "") - .filter((c: string) => /^EQP-\d+$/.test(c)) - .map((c: string) => parseInt(c.replace("EQP-", ""), 10)) - .sort((a: number, b: number) => b - a)[0] || 0; + const openEqCreate = async () => { setEqForm({ - equipment_code: `EQP-${String(maxNum + 1).padStart(3, "0")}`, calibration_period: "12", equipment_status: "NORMAL", }); setEqEditMode(false); + setNumberingRuleId(null); + setPreviewCode(null); setEqModalOpen(true); + try { + const ruleRes = await apiClient.get(`/numbering-rules/by-column/${EQUIPMENT_TABLE}/equipment_code`); + const ruleData = ruleRes.data; + if (ruleData?.success && ruleData?.data?.ruleId) { + const ruleId = ruleData.data.ruleId; + setNumberingRuleId(ruleId); + const prev = await previewNumberingCode(ruleId); + if (prev.success && prev.data?.generatedCode) { + setPreviewCode(prev.data.generatedCode); + } + } else { + // 채번 규칙 없으면 기존 수동 채번 fallback + const maxNum = + equipments + .map((e: any) => e.equipment_code || "") + .filter((c: string) => /^EQP-\d+$/.test(c)) + .map((c: string) => parseInt(c.replace("EQP-", ""), 10)) + .sort((a: number, b: number) => b - a)[0] || 0; + setEqForm((p) => ({ ...p, equipment_code: `EQP-${String(maxNum + 1).padStart(3, "0")}` })); + } + } catch { + // 채번 규칙 조회 실패 시 기존 수동 채번 fallback + const maxNum = + equipments + .map((e: any) => e.equipment_code || "") + .filter((c: string) => /^EQP-\d+$/.test(c)) + .map((c: string) => parseInt(c.replace("EQP-", ""), 10)) + .sort((a: number, b: number) => b - a)[0] || 0; + setEqForm((p) => ({ ...p, equipment_code: `EQP-${String(maxNum + 1).padStart(3, "0")}` })); + } }; const openEqEdit = (row: any) => { setEqForm({ ...row }); @@ -440,7 +521,7 @@ export default function InspectionManagementPage() { setEqModalOpen(true); }; const saveEquipment = async () => { - if (!eqForm.equipment_code) { + if (!numberingRuleId && !eqForm.equipment_code) { toast.error("장비코드는 필수예요"); return; } @@ -454,6 +535,17 @@ export default function InspectionManagementPage() { } setEqSaving(true); try { + let finalCode = eqForm.equipment_code || ""; + if (!eqEditMode && numberingRuleId) { + const allocRes = await allocateNumberingCode(numberingRuleId); + if (allocRes.success && allocRes.data?.generatedCode) { + finalCode = allocRes.data.generatedCode; + } else { + toast.error("채번 코드 할당에 실패했습니다."); + setEqSaving(false); + return; + } + } if (eqEditMode) { await apiClient.put(`/table-management/tables/${EQUIPMENT_TABLE}/edit`, { originalData: { id: eqForm.id }, @@ -461,7 +553,7 @@ export default function InspectionManagementPage() { }); toast.success("검사장비를 수정했어요"); } else { - await apiClient.post(`/table-management/tables/${EQUIPMENT_TABLE}/add`, { id: crypto.randomUUID(), ...eqForm }); + await apiClient.post(`/table-management/tables/${EQUIPMENT_TABLE}/add`, { id: crypto.randomUUID(), ...eqForm, equipment_code: finalCode }); toast.success("검사장비를 등록했어요"); } setEqModalOpen(false); @@ -1032,12 +1124,27 @@ export default function InspectionManagementPage() { - setInspForm((p) => ({ ...p, inspection_code: e.target.value }))} - placeholder="검사코드 입력" - /> + {!inspEditMode && numberingRuleId ? ( + + ) : inspEditMode ? ( + + ) : ( + setInspForm((p) => ({ ...p, inspection_code: e.target.value }))} + placeholder="검사코드 입력" + /> + )}
{/* 유형 (다중선택) */}
@@ -1238,12 +1345,27 @@ export default function InspectionManagementPage() { - setDefForm((p) => ({ ...p, defect_code: e.target.value }))} - placeholder="불량코드 입력" - /> + {!defEditMode && numberingRuleId ? ( + + ) : defEditMode ? ( + + ) : ( + setDefForm((p) => ({ ...p, defect_code: e.target.value }))} + placeholder="불량코드 입력" + /> + )}
{/* 불량유형 */}
@@ -1462,13 +1584,27 @@ export default function InspectionManagementPage() { - setEqForm((p) => ({ ...p, equipment_code: e.target.value }))} - placeholder="장비코드" - disabled={eqEditMode} - /> + {!eqEditMode && numberingRuleId ? ( + + ) : eqEditMode ? ( + + ) : ( + setEqForm((p) => ({ ...p, equipment_code: e.target.value }))} + placeholder="장비코드" + /> + )}