fix: Enhance file handling and inspection method mapping
- Updated fileController to include Cross-Origin-Resource-Policy headers for improved security and file handling. - Added error handling for file streams to ensure robust responses in case of read errors. - Modified materialStatusController to correctly map material IDs to their respective codes for inventory stock queries. - Enhanced moldController to include warranty shot count in mold creation and update processes. - Improved item inspection page by adding inspection method category loading and mapping, ensuring accurate display of method labels in the UI. These changes aim to enhance the overall functionality and user experience across multiple companies by ensuring proper file handling, data mapping, and error management.
This commit is contained in:
@@ -924,12 +924,26 @@ export const previewFile = async (
|
||||
);
|
||||
res.setHeader("Access-Control-Allow-Credentials", "true");
|
||||
|
||||
// 캐시 헤더 설정
|
||||
// Cross-Origin-Resource-Policy: cross-origin 설정
|
||||
// helmet 기본값(same-origin)을 오버라이드하여 v1.vexplor.com에서 api.vexplor.com 이미지 로드 허용
|
||||
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
||||
|
||||
// 파일 크기 및 캐시 헤더 설정
|
||||
const stat = fs.statSync(finalPath);
|
||||
res.setHeader("Content-Length", stat.size);
|
||||
res.setHeader("Cache-Control", "public, max-age=3600");
|
||||
res.setHeader("Content-Type", mimeType);
|
||||
|
||||
// 파일 스트림으로 전송
|
||||
const fileStream = fs.createReadStream(finalPath);
|
||||
fileStream.on("error", (err) => {
|
||||
console.error("파일 스트림 오류:", err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ success: false, message: "파일 읽기 오류" });
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
fileStream.pipe(res);
|
||||
} catch (error) {
|
||||
console.error("파일 미리보기 오류:", error);
|
||||
@@ -1031,9 +1045,20 @@ export const downloadFile = async (
|
||||
`attachment; filename="${encodeURIComponent(fileRecord.real_file_name!)}"`
|
||||
);
|
||||
res.setHeader("Content-Type", "application/octet-stream");
|
||||
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
||||
const stat = fs.statSync(filePath);
|
||||
res.setHeader("Content-Length", stat.size);
|
||||
|
||||
// 파일 스트림 전송
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
fileStream.on("error", (err) => {
|
||||
console.error("파일 스트림 오류:", err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ success: false, message: "파일 읽기 오류" });
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
fileStream.pipe(res);
|
||||
} catch (error) {
|
||||
console.error("파일 다운로드 오류:", error);
|
||||
@@ -1218,10 +1243,21 @@ export const getFileByToken = async (req: Request, res: Response) => {
|
||||
"Content-Disposition",
|
||||
`inline; filename="${encodeURIComponent(fileRecord.real_file_name!)}"`
|
||||
);
|
||||
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
||||
const stat = fs.statSync(filePath);
|
||||
res.setHeader("Content-Length", stat.size);
|
||||
res.setHeader("Cache-Control", "public, max-age=300"); // 5분 캐시
|
||||
|
||||
// 파일 스트림 전송
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
fileStream.on("error", (err) => {
|
||||
console.error("파일 스트림 오류:", err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ success: false, message: "파일 읽기 오류" });
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
fileStream.pipe(res);
|
||||
} catch (error) {
|
||||
console.error("❌ 토큰 파일 접근 오류:", error);
|
||||
|
||||
@@ -226,11 +226,12 @@ export async function getMaterialStatus(
|
||||
return res.json({ success: true, data: [] });
|
||||
}
|
||||
|
||||
// 4) 재고 조회 (창고/위치별)
|
||||
const stockPlaceholders = materialIds
|
||||
// 4) 재고 조회 (창고/위치별) — inventory_stock.item_code는 item_number 기준
|
||||
const materialCodes = materialIds.map((id) => materialMap[id].materialCode);
|
||||
const stockPlaceholders = materialCodes
|
||||
.map((_, i) => `$${i + 1}`)
|
||||
.join(",");
|
||||
const stockParams: any[] = [...materialIds];
|
||||
const stockParams: any[] = [...materialCodes];
|
||||
let stockParamIdx = materialIds.length + 1;
|
||||
|
||||
const stockConditions: string[] = [
|
||||
|
||||
@@ -94,7 +94,7 @@ export async function createMold(req: AuthenticatedRequest, res: Response): Prom
|
||||
mold_code, mold_name, mold_type, category, manufacturer,
|
||||
manufacturing_number, manufacturing_date, cavity_count,
|
||||
shot_count, mold_quantity, base_input_qty, operation_status,
|
||||
remarks, image_path, memo,
|
||||
remarks, image_path, memo, warranty_shot_count,
|
||||
} = req.body;
|
||||
|
||||
if (!mold_code || !mold_name) {
|
||||
@@ -107,15 +107,16 @@ export async function createMold(req: AuthenticatedRequest, res: Response): Prom
|
||||
id, company_code, mold_code, mold_name, mold_type, category,
|
||||
manufacturer, manufacturing_number, manufacturing_date,
|
||||
cavity_count, shot_count, mold_quantity, base_input_qty,
|
||||
operation_status, remarks, image_path, memo, writer, created_date
|
||||
) VALUES (gen_random_uuid()::text,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,NOW())
|
||||
operation_status, remarks, image_path, memo, warranty_shot_count, writer, created_date
|
||||
) VALUES (gen_random_uuid()::text,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,NOW())
|
||||
RETURNING *
|
||||
`;
|
||||
const params = [
|
||||
companyCode, mold_code, mold_name, mold_type || null, category || null,
|
||||
manufacturer || null, manufacturing_number || null, manufacturing_date || null,
|
||||
cavity_count || 0, shot_count || 0, mold_quantity || 1, base_input_qty || 0,
|
||||
operation_status || "ACTIVE", remarks || null, image_path || null, memo || null, userId,
|
||||
operation_status || "ACTIVE", remarks || null, image_path || null, memo || null,
|
||||
warranty_shot_count || 0, userId,
|
||||
];
|
||||
|
||||
const result = await query(sql, params);
|
||||
@@ -139,7 +140,7 @@ export async function updateMold(req: AuthenticatedRequest, res: Response): Prom
|
||||
mold_name, mold_type, category, manufacturer,
|
||||
manufacturing_number, manufacturing_date, cavity_count,
|
||||
shot_count, mold_quantity, base_input_qty, operation_status,
|
||||
remarks, image_path, memo,
|
||||
remarks, image_path, memo, warranty_shot_count,
|
||||
} = req.body;
|
||||
|
||||
const sql = `
|
||||
@@ -153,8 +154,9 @@ export async function updateMold(req: AuthenticatedRequest, res: Response): Prom
|
||||
base_input_qty = COALESCE($10, base_input_qty),
|
||||
operation_status = COALESCE($11, operation_status),
|
||||
remarks = $12, image_path = $13, memo = $14,
|
||||
warranty_shot_count = $15,
|
||||
updated_date = NOW()
|
||||
WHERE mold_code = $15 AND company_code = $16
|
||||
WHERE mold_code = $16 AND company_code = $17
|
||||
RETURNING *
|
||||
`;
|
||||
const params = [
|
||||
@@ -162,7 +164,7 @@ export async function updateMold(req: AuthenticatedRequest, res: Response): Prom
|
||||
manufacturing_number, manufacturing_date,
|
||||
cavity_count, shot_count, mold_quantity, base_input_qty,
|
||||
operation_status, remarks, image_path, memo,
|
||||
moldCode, companyCode,
|
||||
warranty_shot_count || 0, moldCode, companyCode,
|
||||
];
|
||||
|
||||
const result = await query(sql, params);
|
||||
|
||||
@@ -230,11 +230,10 @@ function flattenCategories(items: any[]): { value: string; label: string }[] {
|
||||
const result: { value: string; label: string }[] = [];
|
||||
function walk(arr: any[]) {
|
||||
for (const item of arr) {
|
||||
if (item.value || item.name) {
|
||||
result.push({
|
||||
value: item.value || item.name,
|
||||
label: item.label || item.name || item.value,
|
||||
});
|
||||
const val = item.valueCode || item.value || item.name;
|
||||
const lbl = item.valueLabel || item.label || item.name || val;
|
||||
if (val) {
|
||||
result.push({ value: val, label: lbl });
|
||||
}
|
||||
if (item.children?.length) walk(item.children);
|
||||
}
|
||||
|
||||
@@ -777,12 +777,16 @@ export default function ReceivingPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modalWarehouse) {
|
||||
toast.error("창고를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await createReceiving({
|
||||
inbound_number: modalInboundNo,
|
||||
inbound_date: modalInboundDate,
|
||||
warehouse_code: modalWarehouse || undefined,
|
||||
warehouse_code: modalWarehouse,
|
||||
location_code: modalLocation || undefined,
|
||||
inspector: modalInspector || undefined,
|
||||
manager: modalManager || undefined,
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function ItemInspectionInfoPage() {
|
||||
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
|
||||
const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
|
||||
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
|
||||
/* 검사유형별 검사항목 rows */
|
||||
@@ -118,6 +119,15 @@ export default function ItemInspectionInfoPage() {
|
||||
setInspTypeCatOptions(flatCats);
|
||||
} catch { /* skip */ }
|
||||
|
||||
// 검사방법 카테고리 값 로드 (코드→라벨 매핑용)
|
||||
try {
|
||||
const methodRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_method/values`);
|
||||
const flatMethods: { code: string; label: string }[] = [];
|
||||
const flattenM = (arr: any[]) => { for (const v of arr) { flatMethods.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenM(v.children); } };
|
||||
if (methodRes.data?.data?.length) flattenM(methodRes.data.data);
|
||||
setInspMethodCatOptions(flatMethods);
|
||||
} catch { /* skip */ }
|
||||
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
setUserOptions(users.map((u: any) => ({
|
||||
code: u.user_id || u.id,
|
||||
@@ -221,11 +231,13 @@ export default function ItemInspectionInfoPage() {
|
||||
if (!typeKey) continue;
|
||||
typeFlags[typeKey] = true;
|
||||
if (!rowMap[typeKey]) rowMap[typeKey] = [];
|
||||
const mCode = r.inspection_method || "";
|
||||
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
|
||||
rowMap[typeKey].push({
|
||||
id: r.id,
|
||||
inspection_standard_id: r.inspection_standard_id || "",
|
||||
inspection_detail: r.inspection_item_name || r.inspection_standard || "",
|
||||
inspection_method: r.inspection_method || "",
|
||||
inspection_method: mLabel,
|
||||
apply_process: "",
|
||||
acceptance_criteria: r.pass_criteria || "",
|
||||
is_required: r.is_required === "true" || r.is_required === true,
|
||||
@@ -270,7 +282,9 @@ export default function ItemInspectionInfoPage() {
|
||||
if (r.id !== rowId) return r;
|
||||
if (field === "inspection_standard_id") {
|
||||
const opt = inspOptions.find(o => o.code === value);
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: opt?.method || "" };
|
||||
const methodCode = opt?.method || "";
|
||||
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
|
||||
}
|
||||
return { ...r, [field]: value };
|
||||
}),
|
||||
@@ -471,17 +485,36 @@ export default function ItemInspectionInfoPage() {
|
||||
</TableCell>
|
||||
{ts.visibleColumns.map((col) => renderCell(col.key))}
|
||||
</TableRow>
|
||||
{isExpanded && group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="bg-muted/30 text-xs">
|
||||
{isExpanded && (
|
||||
<TableRow className="bg-muted/30">
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
<TableCell className="pl-6 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell>{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell>{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell>{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell>{row.pass_criteria || "-"}</TableCell>
|
||||
<TableCell colSpan={ts.visibleColumns.length} className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50">
|
||||
<TableHead className="text-[10px] font-bold h-7">검사유형</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사기준</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사항목</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사방법</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">합격기준</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="text-xs">
|
||||
<TableCell className="py-1.5 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell className="py-1.5">{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.pass_criteria || "-"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -230,11 +230,10 @@ function flattenCategories(items: any[]): { value: string; label: string }[] {
|
||||
const result: { value: string; label: string }[] = [];
|
||||
function walk(arr: any[]) {
|
||||
for (const item of arr) {
|
||||
if (item.value || item.name) {
|
||||
result.push({
|
||||
value: item.value || item.name,
|
||||
label: item.label || item.name || item.value,
|
||||
});
|
||||
const val = item.valueCode || item.value || item.name;
|
||||
const lbl = item.valueLabel || item.label || item.name || val;
|
||||
if (val) {
|
||||
result.push({ value: val, label: lbl });
|
||||
}
|
||||
if (item.children?.length) walk(item.children);
|
||||
}
|
||||
|
||||
@@ -777,12 +777,16 @@ export default function ReceivingPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modalWarehouse) {
|
||||
toast.error("창고를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await createReceiving({
|
||||
inbound_number: modalInboundNo,
|
||||
inbound_date: modalInboundDate,
|
||||
warehouse_code: modalWarehouse || undefined,
|
||||
warehouse_code: modalWarehouse,
|
||||
location_code: modalLocation || undefined,
|
||||
inspector: modalInspector || undefined,
|
||||
manager: modalManager || undefined,
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function ItemInspectionInfoPage() {
|
||||
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
|
||||
const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
|
||||
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
|
||||
/* 검사유형별 검사항목 rows */
|
||||
@@ -118,6 +119,15 @@ export default function ItemInspectionInfoPage() {
|
||||
setInspTypeCatOptions(flatCats);
|
||||
} catch { /* skip */ }
|
||||
|
||||
// 검사방법 카테고리 값 로드 (코드→라벨 매핑용)
|
||||
try {
|
||||
const methodRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_method/values`);
|
||||
const flatMethods: { code: string; label: string }[] = [];
|
||||
const flattenM = (arr: any[]) => { for (const v of arr) { flatMethods.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenM(v.children); } };
|
||||
if (methodRes.data?.data?.length) flattenM(methodRes.data.data);
|
||||
setInspMethodCatOptions(flatMethods);
|
||||
} catch { /* skip */ }
|
||||
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
setUserOptions(users.map((u: any) => ({
|
||||
code: u.user_id || u.id,
|
||||
@@ -221,11 +231,13 @@ export default function ItemInspectionInfoPage() {
|
||||
if (!typeKey) continue;
|
||||
typeFlags[typeKey] = true;
|
||||
if (!rowMap[typeKey]) rowMap[typeKey] = [];
|
||||
const mCode = r.inspection_method || "";
|
||||
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
|
||||
rowMap[typeKey].push({
|
||||
id: r.id,
|
||||
inspection_standard_id: r.inspection_standard_id || "",
|
||||
inspection_detail: r.inspection_item_name || r.inspection_standard || "",
|
||||
inspection_method: r.inspection_method || "",
|
||||
inspection_method: mLabel,
|
||||
apply_process: "",
|
||||
acceptance_criteria: r.pass_criteria || "",
|
||||
is_required: r.is_required === "true" || r.is_required === true,
|
||||
@@ -270,7 +282,9 @@ export default function ItemInspectionInfoPage() {
|
||||
if (r.id !== rowId) return r;
|
||||
if (field === "inspection_standard_id") {
|
||||
const opt = inspOptions.find(o => o.code === value);
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: opt?.method || "" };
|
||||
const methodCode = opt?.method || "";
|
||||
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
|
||||
}
|
||||
return { ...r, [field]: value };
|
||||
}),
|
||||
@@ -471,17 +485,36 @@ export default function ItemInspectionInfoPage() {
|
||||
</TableCell>
|
||||
{ts.visibleColumns.map((col) => renderCell(col.key))}
|
||||
</TableRow>
|
||||
{isExpanded && group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="bg-muted/30 text-xs">
|
||||
{isExpanded && (
|
||||
<TableRow className="bg-muted/30">
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
<TableCell className="pl-6 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell>{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell>{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell>{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell>{row.pass_criteria || "-"}</TableCell>
|
||||
<TableCell colSpan={ts.visibleColumns.length} className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50">
|
||||
<TableHead className="text-[10px] font-bold h-7">검사유형</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사기준</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사항목</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사방법</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">합격기준</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="text-xs">
|
||||
<TableCell className="py-1.5 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell className="py-1.5">{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.pass_criteria || "-"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -230,11 +230,10 @@ function flattenCategories(items: any[]): { value: string; label: string }[] {
|
||||
const result: { value: string; label: string }[] = [];
|
||||
function walk(arr: any[]) {
|
||||
for (const item of arr) {
|
||||
if (item.value || item.name) {
|
||||
result.push({
|
||||
value: item.value || item.name,
|
||||
label: item.label || item.name || item.value,
|
||||
});
|
||||
const val = item.valueCode || item.value || item.name;
|
||||
const lbl = item.valueLabel || item.label || item.name || val;
|
||||
if (val) {
|
||||
result.push({ value: val, label: lbl });
|
||||
}
|
||||
if (item.children?.length) walk(item.children);
|
||||
}
|
||||
|
||||
@@ -777,12 +777,16 @@ export default function ReceivingPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modalWarehouse) {
|
||||
toast.error("창고를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await createReceiving({
|
||||
inbound_number: modalInboundNo,
|
||||
inbound_date: modalInboundDate,
|
||||
warehouse_code: modalWarehouse || undefined,
|
||||
warehouse_code: modalWarehouse,
|
||||
location_code: modalLocation || undefined,
|
||||
inspector: modalInspector || undefined,
|
||||
manager: modalManager || undefined,
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function ItemInspectionInfoPage() {
|
||||
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
|
||||
const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
|
||||
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
|
||||
/* 검사유형별 검사항목 rows */
|
||||
@@ -118,6 +119,15 @@ export default function ItemInspectionInfoPage() {
|
||||
setInspTypeCatOptions(flatCats);
|
||||
} catch { /* skip */ }
|
||||
|
||||
// 검사방법 카테고리 값 로드 (코드→라벨 매핑용)
|
||||
try {
|
||||
const methodRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_method/values`);
|
||||
const flatMethods: { code: string; label: string }[] = [];
|
||||
const flattenM = (arr: any[]) => { for (const v of arr) { flatMethods.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenM(v.children); } };
|
||||
if (methodRes.data?.data?.length) flattenM(methodRes.data.data);
|
||||
setInspMethodCatOptions(flatMethods);
|
||||
} catch { /* skip */ }
|
||||
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
setUserOptions(users.map((u: any) => ({
|
||||
code: u.user_id || u.id,
|
||||
@@ -221,11 +231,13 @@ export default function ItemInspectionInfoPage() {
|
||||
if (!typeKey) continue;
|
||||
typeFlags[typeKey] = true;
|
||||
if (!rowMap[typeKey]) rowMap[typeKey] = [];
|
||||
const mCode = r.inspection_method || "";
|
||||
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
|
||||
rowMap[typeKey].push({
|
||||
id: r.id,
|
||||
inspection_standard_id: r.inspection_standard_id || "",
|
||||
inspection_detail: r.inspection_item_name || r.inspection_standard || "",
|
||||
inspection_method: r.inspection_method || "",
|
||||
inspection_method: mLabel,
|
||||
apply_process: "",
|
||||
acceptance_criteria: r.pass_criteria || "",
|
||||
is_required: r.is_required === "true" || r.is_required === true,
|
||||
@@ -270,7 +282,9 @@ export default function ItemInspectionInfoPage() {
|
||||
if (r.id !== rowId) return r;
|
||||
if (field === "inspection_standard_id") {
|
||||
const opt = inspOptions.find(o => o.code === value);
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: opt?.method || "" };
|
||||
const methodCode = opt?.method || "";
|
||||
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
|
||||
}
|
||||
return { ...r, [field]: value };
|
||||
}),
|
||||
@@ -471,17 +485,36 @@ export default function ItemInspectionInfoPage() {
|
||||
</TableCell>
|
||||
{ts.visibleColumns.map((col) => renderCell(col.key))}
|
||||
</TableRow>
|
||||
{isExpanded && group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="bg-muted/30 text-xs">
|
||||
{isExpanded && (
|
||||
<TableRow className="bg-muted/30">
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
<TableCell className="pl-6 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell>{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell>{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell>{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell>{row.pass_criteria || "-"}</TableCell>
|
||||
<TableCell colSpan={ts.visibleColumns.length} className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50">
|
||||
<TableHead className="text-[10px] font-bold h-7">검사유형</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사기준</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사항목</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사방법</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">합격기준</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="text-xs">
|
||||
<TableCell className="py-1.5 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell className="py-1.5">{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.pass_criteria || "-"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -230,11 +230,10 @@ function flattenCategories(items: any[]): { value: string; label: string }[] {
|
||||
const result: { value: string; label: string }[] = [];
|
||||
function walk(arr: any[]) {
|
||||
for (const item of arr) {
|
||||
if (item.value || item.name) {
|
||||
result.push({
|
||||
value: item.value || item.name,
|
||||
label: item.label || item.name || item.value,
|
||||
});
|
||||
const val = item.valueCode || item.value || item.name;
|
||||
const lbl = item.valueLabel || item.label || item.name || val;
|
||||
if (val) {
|
||||
result.push({ value: val, label: lbl });
|
||||
}
|
||||
if (item.children?.length) walk(item.children);
|
||||
}
|
||||
|
||||
@@ -777,12 +777,16 @@ export default function ReceivingPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modalWarehouse) {
|
||||
toast.error("창고를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await createReceiving({
|
||||
inbound_number: modalInboundNo,
|
||||
inbound_date: modalInboundDate,
|
||||
warehouse_code: modalWarehouse || undefined,
|
||||
warehouse_code: modalWarehouse,
|
||||
location_code: modalLocation || undefined,
|
||||
inspector: modalInspector || undefined,
|
||||
manager: modalManager || undefined,
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function ItemInspectionInfoPage() {
|
||||
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
|
||||
const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
|
||||
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
|
||||
/* 검사유형별 검사항목 rows */
|
||||
@@ -118,6 +119,15 @@ export default function ItemInspectionInfoPage() {
|
||||
setInspTypeCatOptions(flatCats);
|
||||
} catch { /* skip */ }
|
||||
|
||||
// 검사방법 카테고리 값 로드 (코드→라벨 매핑용)
|
||||
try {
|
||||
const methodRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_method/values`);
|
||||
const flatMethods: { code: string; label: string }[] = [];
|
||||
const flattenM = (arr: any[]) => { for (const v of arr) { flatMethods.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenM(v.children); } };
|
||||
if (methodRes.data?.data?.length) flattenM(methodRes.data.data);
|
||||
setInspMethodCatOptions(flatMethods);
|
||||
} catch { /* skip */ }
|
||||
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
setUserOptions(users.map((u: any) => ({
|
||||
code: u.user_id || u.id,
|
||||
@@ -221,11 +231,13 @@ export default function ItemInspectionInfoPage() {
|
||||
if (!typeKey) continue;
|
||||
typeFlags[typeKey] = true;
|
||||
if (!rowMap[typeKey]) rowMap[typeKey] = [];
|
||||
const mCode = r.inspection_method || "";
|
||||
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
|
||||
rowMap[typeKey].push({
|
||||
id: r.id,
|
||||
inspection_standard_id: r.inspection_standard_id || "",
|
||||
inspection_detail: r.inspection_item_name || r.inspection_standard || "",
|
||||
inspection_method: r.inspection_method || "",
|
||||
inspection_method: mLabel,
|
||||
apply_process: "",
|
||||
acceptance_criteria: r.pass_criteria || "",
|
||||
is_required: r.is_required === "true" || r.is_required === true,
|
||||
@@ -270,7 +282,9 @@ export default function ItemInspectionInfoPage() {
|
||||
if (r.id !== rowId) return r;
|
||||
if (field === "inspection_standard_id") {
|
||||
const opt = inspOptions.find(o => o.code === value);
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: opt?.method || "" };
|
||||
const methodCode = opt?.method || "";
|
||||
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
|
||||
}
|
||||
return { ...r, [field]: value };
|
||||
}),
|
||||
@@ -471,17 +485,36 @@ export default function ItemInspectionInfoPage() {
|
||||
</TableCell>
|
||||
{ts.visibleColumns.map((col) => renderCell(col.key))}
|
||||
</TableRow>
|
||||
{isExpanded && group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="bg-muted/30 text-xs">
|
||||
{isExpanded && (
|
||||
<TableRow className="bg-muted/30">
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
<TableCell className="pl-6 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell>{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell>{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell>{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell>{row.pass_criteria || "-"}</TableCell>
|
||||
<TableCell colSpan={ts.visibleColumns.length} className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50">
|
||||
<TableHead className="text-[10px] font-bold h-7">검사유형</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사기준</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사항목</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사방법</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">합격기준</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="text-xs">
|
||||
<TableCell className="py-1.5 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell className="py-1.5">{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.pass_criteria || "-"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -230,11 +230,10 @@ function flattenCategories(items: any[]): { value: string; label: string }[] {
|
||||
const result: { value: string; label: string }[] = [];
|
||||
function walk(arr: any[]) {
|
||||
for (const item of arr) {
|
||||
if (item.value || item.name) {
|
||||
result.push({
|
||||
value: item.value || item.name,
|
||||
label: item.label || item.name || item.value,
|
||||
});
|
||||
const val = item.valueCode || item.value || item.name;
|
||||
const lbl = item.valueLabel || item.label || item.name || val;
|
||||
if (val) {
|
||||
result.push({ value: val, label: lbl });
|
||||
}
|
||||
if (item.children?.length) walk(item.children);
|
||||
}
|
||||
|
||||
@@ -777,12 +777,16 @@ export default function ReceivingPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modalWarehouse) {
|
||||
toast.error("창고를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await createReceiving({
|
||||
inbound_number: modalInboundNo,
|
||||
inbound_date: modalInboundDate,
|
||||
warehouse_code: modalWarehouse || undefined,
|
||||
warehouse_code: modalWarehouse,
|
||||
location_code: modalLocation || undefined,
|
||||
inspector: modalInspector || undefined,
|
||||
manager: modalManager || undefined,
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function ItemInspectionInfoPage() {
|
||||
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
|
||||
const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
|
||||
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
|
||||
/* 검사유형별 검사항목 rows */
|
||||
@@ -118,6 +119,15 @@ export default function ItemInspectionInfoPage() {
|
||||
setInspTypeCatOptions(flatCats);
|
||||
} catch { /* skip */ }
|
||||
|
||||
// 검사방법 카테고리 값 로드 (코드→라벨 매핑용)
|
||||
try {
|
||||
const methodRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_method/values`);
|
||||
const flatMethods: { code: string; label: string }[] = [];
|
||||
const flattenM = (arr: any[]) => { for (const v of arr) { flatMethods.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenM(v.children); } };
|
||||
if (methodRes.data?.data?.length) flattenM(methodRes.data.data);
|
||||
setInspMethodCatOptions(flatMethods);
|
||||
} catch { /* skip */ }
|
||||
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
setUserOptions(users.map((u: any) => ({
|
||||
code: u.user_id || u.id,
|
||||
@@ -221,11 +231,13 @@ export default function ItemInspectionInfoPage() {
|
||||
if (!typeKey) continue;
|
||||
typeFlags[typeKey] = true;
|
||||
if (!rowMap[typeKey]) rowMap[typeKey] = [];
|
||||
const mCode = r.inspection_method || "";
|
||||
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
|
||||
rowMap[typeKey].push({
|
||||
id: r.id,
|
||||
inspection_standard_id: r.inspection_standard_id || "",
|
||||
inspection_detail: r.inspection_item_name || r.inspection_standard || "",
|
||||
inspection_method: r.inspection_method || "",
|
||||
inspection_method: mLabel,
|
||||
apply_process: "",
|
||||
acceptance_criteria: r.pass_criteria || "",
|
||||
is_required: r.is_required === "true" || r.is_required === true,
|
||||
@@ -270,7 +282,9 @@ export default function ItemInspectionInfoPage() {
|
||||
if (r.id !== rowId) return r;
|
||||
if (field === "inspection_standard_id") {
|
||||
const opt = inspOptions.find(o => o.code === value);
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: opt?.method || "" };
|
||||
const methodCode = opt?.method || "";
|
||||
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
|
||||
}
|
||||
return { ...r, [field]: value };
|
||||
}),
|
||||
@@ -471,17 +485,36 @@ export default function ItemInspectionInfoPage() {
|
||||
</TableCell>
|
||||
{ts.visibleColumns.map((col) => renderCell(col.key))}
|
||||
</TableRow>
|
||||
{isExpanded && group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="bg-muted/30 text-xs">
|
||||
{isExpanded && (
|
||||
<TableRow className="bg-muted/30">
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
<TableCell className="pl-6 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell>{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell>{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell>{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell>{row.pass_criteria || "-"}</TableCell>
|
||||
<TableCell colSpan={ts.visibleColumns.length} className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50">
|
||||
<TableHead className="text-[10px] font-bold h-7">검사유형</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사기준</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사항목</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사방법</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">합격기준</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="text-xs">
|
||||
<TableCell className="py-1.5 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell className="py-1.5">{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.pass_criteria || "-"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -230,11 +230,10 @@ function flattenCategories(items: any[]): { value: string; label: string }[] {
|
||||
const result: { value: string; label: string }[] = [];
|
||||
function walk(arr: any[]) {
|
||||
for (const item of arr) {
|
||||
if (item.value || item.name) {
|
||||
result.push({
|
||||
value: item.value || item.name,
|
||||
label: item.label || item.name || item.value,
|
||||
});
|
||||
const val = item.valueCode || item.value || item.name;
|
||||
const lbl = item.valueLabel || item.label || item.name || val;
|
||||
if (val) {
|
||||
result.push({ value: val, label: lbl });
|
||||
}
|
||||
if (item.children?.length) walk(item.children);
|
||||
}
|
||||
|
||||
@@ -777,12 +777,16 @@ export default function ReceivingPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modalWarehouse) {
|
||||
toast.error("창고를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await createReceiving({
|
||||
inbound_number: modalInboundNo,
|
||||
inbound_date: modalInboundDate,
|
||||
warehouse_code: modalWarehouse || undefined,
|
||||
warehouse_code: modalWarehouse,
|
||||
location_code: modalLocation || undefined,
|
||||
inspector: modalInspector || undefined,
|
||||
manager: modalManager || undefined,
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function ItemInspectionInfoPage() {
|
||||
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
|
||||
const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
|
||||
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
|
||||
/* 검사유형별 검사항목 rows */
|
||||
@@ -118,6 +119,15 @@ export default function ItemInspectionInfoPage() {
|
||||
setInspTypeCatOptions(flatCats);
|
||||
} catch { /* skip */ }
|
||||
|
||||
// 검사방법 카테고리 값 로드 (코드→라벨 매핑용)
|
||||
try {
|
||||
const methodRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_method/values`);
|
||||
const flatMethods: { code: string; label: string }[] = [];
|
||||
const flattenM = (arr: any[]) => { for (const v of arr) { flatMethods.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenM(v.children); } };
|
||||
if (methodRes.data?.data?.length) flattenM(methodRes.data.data);
|
||||
setInspMethodCatOptions(flatMethods);
|
||||
} catch { /* skip */ }
|
||||
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
setUserOptions(users.map((u: any) => ({
|
||||
code: u.user_id || u.id,
|
||||
@@ -221,11 +231,13 @@ export default function ItemInspectionInfoPage() {
|
||||
if (!typeKey) continue;
|
||||
typeFlags[typeKey] = true;
|
||||
if (!rowMap[typeKey]) rowMap[typeKey] = [];
|
||||
const mCode = r.inspection_method || "";
|
||||
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
|
||||
rowMap[typeKey].push({
|
||||
id: r.id,
|
||||
inspection_standard_id: r.inspection_standard_id || "",
|
||||
inspection_detail: r.inspection_item_name || r.inspection_standard || "",
|
||||
inspection_method: r.inspection_method || "",
|
||||
inspection_method: mLabel,
|
||||
apply_process: "",
|
||||
acceptance_criteria: r.pass_criteria || "",
|
||||
is_required: r.is_required === "true" || r.is_required === true,
|
||||
@@ -270,7 +282,9 @@ export default function ItemInspectionInfoPage() {
|
||||
if (r.id !== rowId) return r;
|
||||
if (field === "inspection_standard_id") {
|
||||
const opt = inspOptions.find(o => o.code === value);
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: opt?.method || "" };
|
||||
const methodCode = opt?.method || "";
|
||||
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
|
||||
}
|
||||
return { ...r, [field]: value };
|
||||
}),
|
||||
@@ -471,17 +485,36 @@ export default function ItemInspectionInfoPage() {
|
||||
</TableCell>
|
||||
{ts.visibleColumns.map((col) => renderCell(col.key))}
|
||||
</TableRow>
|
||||
{isExpanded && group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="bg-muted/30 text-xs">
|
||||
{isExpanded && (
|
||||
<TableRow className="bg-muted/30">
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
<TableCell className="pl-6 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell>{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell>{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell>{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell>{row.pass_criteria || "-"}</TableCell>
|
||||
<TableCell colSpan={ts.visibleColumns.length} className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50">
|
||||
<TableHead className="text-[10px] font-bold h-7">검사유형</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사기준</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사항목</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사방법</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">합격기준</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="text-xs">
|
||||
<TableCell className="py-1.5 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell className="py-1.5">{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.pass_criteria || "-"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -230,11 +230,10 @@ function flattenCategories(items: any[]): { value: string; label: string }[] {
|
||||
const result: { value: string; label: string }[] = [];
|
||||
function walk(arr: any[]) {
|
||||
for (const item of arr) {
|
||||
if (item.value || item.name) {
|
||||
result.push({
|
||||
value: item.value || item.name,
|
||||
label: item.label || item.name || item.value,
|
||||
});
|
||||
const val = item.valueCode || item.value || item.name;
|
||||
const lbl = item.valueLabel || item.label || item.name || val;
|
||||
if (val) {
|
||||
result.push({ value: val, label: lbl });
|
||||
}
|
||||
if (item.children?.length) walk(item.children);
|
||||
}
|
||||
|
||||
@@ -777,12 +777,16 @@ export default function ReceivingPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modalWarehouse) {
|
||||
toast.error("창고를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await createReceiving({
|
||||
inbound_number: modalInboundNo,
|
||||
inbound_date: modalInboundDate,
|
||||
warehouse_code: modalWarehouse || undefined,
|
||||
warehouse_code: modalWarehouse,
|
||||
location_code: modalLocation || undefined,
|
||||
inspector: modalInspector || undefined,
|
||||
manager: modalManager || undefined,
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function ItemInspectionInfoPage() {
|
||||
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
|
||||
const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
|
||||
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
|
||||
|
||||
/* 검사유형별 검사항목 rows */
|
||||
@@ -118,6 +119,15 @@ export default function ItemInspectionInfoPage() {
|
||||
setInspTypeCatOptions(flatCats);
|
||||
} catch { /* skip */ }
|
||||
|
||||
// 검사방법 카테고리 값 로드 (코드→라벨 매핑용)
|
||||
try {
|
||||
const methodRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_method/values`);
|
||||
const flatMethods: { code: string; label: string }[] = [];
|
||||
const flattenM = (arr: any[]) => { for (const v of arr) { flatMethods.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenM(v.children); } };
|
||||
if (methodRes.data?.data?.length) flattenM(methodRes.data.data);
|
||||
setInspMethodCatOptions(flatMethods);
|
||||
} catch { /* skip */ }
|
||||
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
setUserOptions(users.map((u: any) => ({
|
||||
code: u.user_id || u.id,
|
||||
@@ -221,11 +231,13 @@ export default function ItemInspectionInfoPage() {
|
||||
if (!typeKey) continue;
|
||||
typeFlags[typeKey] = true;
|
||||
if (!rowMap[typeKey]) rowMap[typeKey] = [];
|
||||
const mCode = r.inspection_method || "";
|
||||
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
|
||||
rowMap[typeKey].push({
|
||||
id: r.id,
|
||||
inspection_standard_id: r.inspection_standard_id || "",
|
||||
inspection_detail: r.inspection_item_name || r.inspection_standard || "",
|
||||
inspection_method: r.inspection_method || "",
|
||||
inspection_method: mLabel,
|
||||
apply_process: "",
|
||||
acceptance_criteria: r.pass_criteria || "",
|
||||
is_required: r.is_required === "true" || r.is_required === true,
|
||||
@@ -270,7 +282,9 @@ export default function ItemInspectionInfoPage() {
|
||||
if (r.id !== rowId) return r;
|
||||
if (field === "inspection_standard_id") {
|
||||
const opt = inspOptions.find(o => o.code === value);
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: opt?.method || "" };
|
||||
const methodCode = opt?.method || "";
|
||||
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
|
||||
return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
|
||||
}
|
||||
return { ...r, [field]: value };
|
||||
}),
|
||||
@@ -471,17 +485,36 @@ export default function ItemInspectionInfoPage() {
|
||||
</TableCell>
|
||||
{ts.visibleColumns.map((col) => renderCell(col.key))}
|
||||
</TableRow>
|
||||
{isExpanded && group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="bg-muted/30 text-xs">
|
||||
{isExpanded && (
|
||||
<TableRow className="bg-muted/30">
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
<TableCell className="pl-6 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell>{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell>{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell>{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell>{row.pass_criteria || "-"}</TableCell>
|
||||
<TableCell colSpan={ts.visibleColumns.length} className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50">
|
||||
<TableHead className="text-[10px] font-bold h-7">검사유형</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사기준</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사항목</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">검사방법</TableHead>
|
||||
<TableHead className="text-[10px] font-bold h-7">합격기준</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{group.rows.filter((r: any) => r.inspection_standard_id).map((row: any) => (
|
||||
<TableRow key={row.id} className="text-xs">
|
||||
<TableCell className="py-1.5 text-muted-foreground">{row.inspection_type}</TableCell>
|
||||
<TableCell className="py-1.5">{resolveInspLabel(row.inspection_standard_id)}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_item_name || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.inspection_method || "-"}</TableCell>
|
||||
<TableCell className="py-1.5">{row.pass_criteria || "-"}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user