2026-03-20 11:58:01 +09:00
/ * *
* 공 정 정 보 관 리 컨 트 롤 러
* - 공 정 마 스 터 CRUD
* - 공 정 별 설 비 관 리
* - 품 목 별 라 우 팅 관 리
* /
import { Response } from "express" ;
import { AuthenticatedRequest } from "../types/auth" ;
import { pool } from "../database/db" ;
import { logger } from "../utils/logger" ;
// ═══════════════════════════════════════════
// 공정 마스터 CRUD
// ═══════════════════════════════════════════
export async function getProcessList ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { processCode , processName , processType , useYn } = req . query ;
const conditions : string [ ] = [ ] ;
const params : any [ ] = [ ] ;
let idx = 1 ;
if ( companyCode !== "*" ) {
conditions . push ( ` company_code = $ ${ idx ++ } ` ) ;
params . push ( companyCode ) ;
}
if ( processCode ) {
conditions . push ( ` process_code ILIKE $ ${ idx ++ } ` ) ;
params . push ( ` % ${ processCode } % ` ) ;
}
if ( processName ) {
conditions . push ( ` process_name ILIKE $ ${ idx ++ } ` ) ;
params . push ( ` % ${ processName } % ` ) ;
}
if ( processType ) {
conditions . push ( ` process_type = $ ${ idx ++ } ` ) ;
params . push ( processType ) ;
}
if ( useYn ) {
conditions . push ( ` use_yn = $ ${ idx ++ } ` ) ;
params . push ( useYn ) ;
}
const where = conditions . length > 0 ? ` WHERE ${ conditions . join ( " AND " ) } ` : "" ;
const result = await pool . query (
` SELECT * FROM process_mng ${ where } ORDER BY process_code ` ,
params
) ;
return res . json ( { success : true , data : result.rows } ) ;
} catch ( error : any ) {
logger . error ( "공정 목록 조회 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function createProcess ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const writer = req . user ! . userId ;
const { process_name , process_type , standard_time , worker_count , use_yn } = req . body ;
// 공정코드 자동 채번: PROC-001, PROC-002, ...
const seqRes = await pool . query (
` SELECT process_code FROM process_mng WHERE company_code = $ 1 AND process_code LIKE 'PROC-%' ORDER BY process_code DESC LIMIT 1 ` ,
[ companyCode ]
) ;
let nextNum = 1 ;
if ( seqRes . rowCount ! > 0 ) {
const lastCode = seqRes . rows [ 0 ] . process_code ;
const numPart = parseInt ( lastCode . replace ( "PROC-" , "" ) , 10 ) ;
if ( ! isNaN ( numPart ) ) nextNum = numPart + 1 ;
}
const processCode = ` PROC- ${ String ( nextNum ) . padStart ( 3 , "0" ) } ` ;
const result = await pool . query (
` INSERT INTO process_mng (id, company_code, process_code, process_name, process_type, standard_time, worker_count, use_yn, writer)
VALUES ( gen_random_uuid ( ) : : text , $1 , $2 , $3 , $4 , $5 , $6 , $7 , $8 ) RETURNING * ` ,
[ companyCode , processCode , process_name , process_type , standard_time || "0" , worker_count || "0" , use_yn || "Y" , writer ]
) ;
return res . json ( { success : true , data : result.rows [ 0 ] } ) ;
} catch ( error : any ) {
logger . error ( "공정 등록 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function updateProcess ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { id } = req . params ;
const { process_name , process_type , standard_time , worker_count , use_yn } = req . body ;
const result = await pool . query (
` UPDATE process_mng SET process_name= $ 1, process_type= $ 2, standard_time= $ 3, worker_count= $ 4, use_yn= $ 5, updated_date=NOW()
WHERE id = $6 AND company_code = $7 RETURNING * ` ,
[ process_name , process_type , standard_time , worker_count , use_yn , id , companyCode ]
) ;
if ( result . rowCount === 0 ) {
return res . status ( 404 ) . json ( { success : false , message : "공정을 찾을 수 없습니다." } ) ;
}
return res . json ( { success : true , data : result.rows [ 0 ] } ) ;
} catch ( error : any ) {
logger . error ( "공정 수정 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function deleteProcesses ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { ids } = req . body ;
if ( ! ids || ! Array . isArray ( ids ) || ids . length === 0 ) {
return res . status ( 400 ) . json ( { success : false , message : "삭제할 공정을 선택해주세요." } ) ;
}
const placeholders = ids . map ( ( _ : any , i : number ) = > ` $ ${ i + 1 } ` ) . join ( "," ) ;
// 설비 매핑도 삭제
await pool . query (
` DELETE FROM process_equipment WHERE process_code IN (SELECT process_code FROM process_mng WHERE id IN ( ${ placeholders } ) AND company_code = $ ${ ids . length + 1 } ) ` ,
[ . . . ids , companyCode ]
) ;
const result = await pool . query (
` DELETE FROM process_mng WHERE id IN ( ${ placeholders } ) AND company_code = $ ${ ids . length + 1 } RETURNING id ` ,
[ . . . ids , companyCode ]
) ;
return res . json ( { success : true , deletedCount : result.rowCount } ) ;
} catch ( error : any ) {
logger . error ( "공정 삭제 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
// ═══════════════════════════════════════════
// 공정별 설비 관리
// ═══════════════════════════════════════════
export async function getProcessEquipments ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { processCode } = req . params ;
const result = await pool . query (
` SELECT pe.*, ei.equipment_name
FROM process_equipment pe
LEFT JOIN equipment_info ei ON pe . equipment_code = ei . equipment_code AND pe . company_code = ei . company_code
WHERE pe . process_code = $1 AND pe . company_code = $2
ORDER BY pe . equipment_code ` ,
[ processCode , companyCode ]
) ;
return res . json ( { success : true , data : result.rows } ) ;
} catch ( error : any ) {
logger . error ( "공정 설비 조회 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function addProcessEquipment ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const writer = req . user ! . userId ;
const { process_code , equipment_code } = req . body ;
const dupCheck = await pool . query (
` SELECT id FROM process_equipment WHERE process_code= $ 1 AND equipment_code= $ 2 AND company_code= $ 3 ` ,
[ process_code , equipment_code , companyCode ]
) ;
if ( dupCheck . rowCount ! > 0 ) {
return res . status ( 400 ) . json ( { success : false , message : "이미 등록된 설비입니다." } ) ;
}
const result = await pool . query (
` INSERT INTO process_equipment (id, company_code, process_code, equipment_code, writer)
VALUES ( gen_random_uuid ( ) : : text , $1 , $2 , $3 , $4 ) RETURNING * ` ,
[ companyCode , process_code , equipment_code , writer ]
) ;
return res . json ( { success : true , data : result.rows [ 0 ] } ) ;
} catch ( error : any ) {
logger . error ( "공정 설비 등록 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function removeProcessEquipment ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { id } = req . params ;
await pool . query (
` DELETE FROM process_equipment WHERE id= $ 1 AND company_code= $ 2 ` ,
[ id , companyCode ]
) ;
return res . json ( { success : true } ) ;
} catch ( error : any ) {
logger . error ( "공정 설비 제거 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function getEquipmentList ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const condition = companyCode === "*" ? "" : ` WHERE company_code = $ 1 ` ;
const params = companyCode === "*" ? [ ] : [ companyCode ] ;
const result = await pool . query (
` SELECT id, equipment_code, equipment_name FROM equipment_info ${ condition } ORDER BY equipment_code ` ,
params
) ;
return res . json ( { success : true , data : result.rows } ) ;
} catch ( error : any ) {
logger . error ( "설비 목록 조회 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
// ═══════════════════════════════════════════
// 품목별 라우팅 관리
// ═══════════════════════════════════════════
export async function getItemsForRouting ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { search } = req . query ;
const conditions : string [ ] = [ "i.company_code = rv.company_code" ] ;
const params : any [ ] = [ ] ;
let idx = 1 ;
if ( companyCode !== "*" ) {
conditions . push ( ` i.company_code = $ ${ idx ++ } ` ) ;
params . push ( companyCode ) ;
}
if ( search ) {
conditions . push ( ` (i.item_number ILIKE $ ${ idx } OR i.item_name ILIKE $ ${ idx } ) ` ) ;
params . push ( ` % ${ search } % ` ) ;
idx ++ ;
}
const where = conditions . length > 0 ? ` WHERE ${ conditions . join ( " AND " ) } ` : "" ;
const result = await pool . query (
` SELECT DISTINCT i.id, i.item_number, i.item_name, i.size, i.unit, i.type
FROM item_info i
INNER JOIN item_routing_version rv ON rv . item_code = i . item_number AND rv . company_code = i . company_code
$ { where }
ORDER BY i . item_number LIMIT 200 ` ,
params
) ;
return res . json ( { success : true , data : result.rows } ) ;
} catch ( error : any ) {
logger . error ( "라우팅 등록 품목 조회 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function searchAllItems ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { search } = req . query ;
const conditions : string [ ] = [ ] ;
const params : any [ ] = [ ] ;
let idx = 1 ;
if ( companyCode !== "*" ) {
conditions . push ( ` company_code = $ ${ idx ++ } ` ) ;
params . push ( companyCode ) ;
}
if ( search ) {
conditions . push ( ` (item_number ILIKE $ ${ idx } OR item_name ILIKE $ ${ idx } ) ` ) ;
params . push ( ` % ${ search } % ` ) ;
idx ++ ;
}
const where = conditions . length > 0 ? ` WHERE ${ conditions . join ( " AND " ) } ` : "" ;
const result = await pool . query (
` SELECT id, item_number, item_name, size, unit, type FROM item_info ${ where } ORDER BY item_number LIMIT 200 ` ,
params
) ;
return res . json ( { success : true , data : result.rows } ) ;
} catch ( error : any ) {
logger . error ( "전체 품목 검색 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function getRoutingVersions ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { itemCode } = req . params ;
const result = await pool . query (
` SELECT * FROM item_routing_version WHERE item_code= $ 1 AND company_code= $ 2 ORDER BY created_date ` ,
[ itemCode , companyCode ]
) ;
return res . json ( { success : true , data : result.rows } ) ;
} catch ( error : any ) {
logger . error ( "라우팅 버전 조회 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function createRoutingVersion ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const writer = req . user ! . userId ;
const { item_code , version_name , description , is_default } = req . body ;
if ( is_default ) {
await pool . query (
` UPDATE item_routing_version SET is_default=false WHERE item_code= $ 1 AND company_code= $ 2 ` ,
[ item_code , companyCode ]
) ;
}
const result = await pool . query (
` INSERT INTO item_routing_version (id, company_code, item_code, version_name, description, is_default, writer)
VALUES ( gen_random_uuid ( ) : : text , $1 , $2 , $3 , $4 , $5 , $6 ) RETURNING * ` ,
[ companyCode , item_code , version_name , description || "" , is_default || false , writer ]
) ;
return res . json ( { success : true , data : result.rows [ 0 ] } ) ;
} catch ( error : any ) {
logger . error ( "라우팅 버전 생성 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function deleteRoutingVersion ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { id } = req . params ;
await pool . query (
` DELETE FROM item_routing_detail WHERE routing_version_id= $ 1 AND company_code= $ 2 ` ,
[ id , companyCode ]
) ;
await pool . query (
` DELETE FROM item_routing_version WHERE id= $ 1 AND company_code= $ 2 ` ,
[ id , companyCode ]
) ;
return res . json ( { success : true } ) ;
} catch ( error : any ) {
logger . error ( "라우팅 버전 삭제 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function getRoutingDetails ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { versionId } = req . params ;
const result = await pool . query (
` SELECT rd.*, pm.process_name
FROM item_routing_detail rd
LEFT JOIN process_mng pm ON rd . process_code = pm . process_code AND rd . company_code = pm . company_code
WHERE rd . routing_version_id = $1 AND rd . company_code = $2
ORDER BY CAST ( rd . seq_no AS INTEGER ) ` ,
[ versionId , companyCode ]
) ;
return res . json ( { success : true , data : result.rows } ) ;
} catch ( error : any ) {
logger . error ( "라우팅 상세 조회 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
export async function saveRoutingDetails ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const writer = req . user ! . userId ;
const { versionId } = req . params ;
const { details } = req . body ;
const client = await pool . connect ( ) ;
try {
await client . query ( "BEGIN" ) ;
// 기존 상세 삭제 후 재입력
await client . query (
` DELETE FROM item_routing_detail WHERE routing_version_id= $ 1 AND company_code= $ 2 ` ,
[ versionId , companyCode ]
) ;
for ( const d of details ) {
await client . query (
` INSERT INTO item_routing_detail (id, company_code, routing_version_id, seq_no, process_code, is_required, is_fixed_order, work_type, standard_time, outsource_supplier, writer)
VALUES ( gen_random_uuid ( ) : : text , $1 , $2 , $3 , $4 , $5 , $6 , $7 , $8 , $9 , $10 ) ` ,
[ companyCode , versionId , d . seq_no , d . process_code , d . is_required || "Y" , d . is_fixed_order || "Y" , d . work_type || "내부" , d . standard_time || "0" , d . outsource_supplier || "" , writer ]
) ;
}
await client . query ( "COMMIT" ) ;
return res . json ( { success : true } ) ;
} catch ( err ) {
await client . query ( "ROLLBACK" ) ;
throw err ;
} finally {
client . release ( ) ;
}
} catch ( error : any ) {
logger . error ( "라우팅 상세 저장 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}
2026-03-20 13:46:30 +09:00
// ═══════════════════════════════════════════
// BOM 구성 자재 조회 (품목코드 기반)
// ═══════════════════════════════════════════
export async function getBomMaterials ( req : AuthenticatedRequest , res : Response ) {
try {
const companyCode = req . user ! . companyCode ;
const { itemCode } = req . params ;
if ( ! itemCode ) {
return res . status ( 400 ) . json ( { success : false , message : "itemCode는 필수입니다" } ) ;
}
const query = `
SELECT
bd . id ,
bd . child_item_id ,
bd . quantity ,
bd . unit as detail_unit ,
bd . process_type ,
i . item_name as child_item_name ,
i . item_number as child_item_code ,
i . type as child_item_type ,
i . unit as item_unit
FROM bom b
JOIN bom_detail bd ON b . id = bd . bom_id AND b . company_code = bd . company_code
LEFT JOIN item_info i ON bd . child_item_id = i . id AND bd . company_code = i . company_code
WHERE b . item_code = $1 AND b . company_code = $2
ORDER BY bd . seq_no ASC , bd . created_date ASC
` ;
const result = await pool . query ( query , [ itemCode , companyCode ] ) ;
logger . info ( "BOM 자재 조회 성공" , { companyCode , itemCode , count : result.rowCount } ) ;
return res . json ( { success : true , data : result.rows } ) ;
} catch ( error : any ) {
logger . error ( "BOM 자재 조회 실패" , { error : error.message } ) ;
return res . status ( 500 ) . json ( { success : false , message : error.message } ) ;
}
}