feat: Add Smart Excel Upload functionality for item inspection

- Introduced a new SmartExcelUploadModal component to facilitate bulk item inspection uploads via Excel.
- Implemented logic for downloading templates, validating uploaded files, and parsing data for inspection criteria.
- Enhanced the item inspection page to support dynamic loading of item process mappings and reference data for improved user experience.
- Added necessary types and utility functions for template generation and parsing, ensuring robust handling of Excel data.
- These changes aim to streamline the item inspection process and improve data management across multiple company implementations.
This commit is contained in:
kjs
2026-04-15 14:23:44 +09:00
parent 7aaf264661
commit dffa16f3e5
9 changed files with 2271 additions and 2 deletions

View File

@@ -372,6 +372,69 @@ export async function getRoutingVersions(req: AuthenticatedRequest, res: Respons
}
}
// ─── 품목별 라우팅 벌크 조회 (엑셀 업로드용) ───
export async function getRoutingVersionsBulk(req: AuthenticatedRequest, res: Response) {
try {
const companyCode = req.user!.companyCode;
const { itemCodes } = req.body as { itemCodes: string[] };
if (!itemCodes || !Array.isArray(itemCodes) || itemCodes.length === 0) {
return res.json({ success: true, data: {} });
}
const pool = getPool();
const result: Record<string, { code: string; name: string }[]> = {};
// 청크 단위로 분할 (PostgreSQL placeholder 제한 대응)
const CHUNK_SIZE = 5000;
for (let ci = 0; ci < itemCodes.length; ci += CHUNK_SIZE) {
const chunk = itemCodes.slice(ci, ci + CHUNK_SIZE);
// 1. 기본 라우팅 버전 조회
const placeholders = chunk.map((_, i) => `$${i + 2}`).join(",");
const versionsResult = await pool.query(
`SELECT DISTINCT ON (item_code) id, item_code, version_name
FROM item_routing_version
WHERE company_code = $1 AND item_code IN (${placeholders})
ORDER BY item_code, is_default DESC, created_date DESC`,
[companyCode, ...chunk]
);
if (versionsResult.rows.length === 0) continue;
// 2. 라우팅 디테일 조회
const versionIds = versionsResult.rows.map((v: any) => v.id);
const vPlaceholders = versionIds.map((_: any, i: number) => `$${i + 2}`).join(",");
const detailsResult = await pool.query(
`SELECT rd.routing_version_id, rd.process_code,
COALESCE(p.process_name, rd.process_code) AS process_name
FROM item_routing_detail rd
LEFT JOIN process_mng p ON p.process_code = rd.process_code AND p.company_code = rd.company_code
WHERE rd.company_code = $1 AND rd.routing_version_id IN (${vPlaceholders})
ORDER BY rd.seq_no::integer`,
[companyCode, ...versionIds]
);
// 3. 매핑
const versionToItem: Record<string, string> = {};
for (const v of versionsResult.rows) {
versionToItem[v.id] = v.item_code;
}
for (const d of detailsResult.rows) {
const itemCode = versionToItem[d.routing_version_id];
if (!itemCode) continue;
if (!result[itemCode]) result[itemCode] = [];
result[itemCode].push({ code: d.process_code, name: d.process_name });
}
}
return res.json({ success: true, data: result });
} catch (error: any) {
logger.error("벌크 라우팅 조회 실패", { error: error.message });
return res.status(500).json({ success: false, message: error.message });
}
}
// ─── 작업지시 라우팅 변경 ───
export async function updateRouting(req: AuthenticatedRequest, res: Response) {
try {

View File

@@ -15,6 +15,9 @@ router.get("/source/production-plan", ctrl.getProductionPlanSource);
router.get("/equipment", ctrl.getEquipmentList);
router.get("/employees", ctrl.getEmployeeList);
// 벌크 라우팅 조회 (품목별 공정 일괄 조회)
router.post("/routing-versions-bulk", ctrl.getRoutingVersionsBulk);
// 라우팅 & 공정작업기준
router.get("/:wiNo/routing-versions/:itemCode", ctrl.getRoutingVersions);
router.put("/:wiNo/routing", ctrl.updateRouting);