|
|
|
@@ -29,7 +29,7 @@ async function copyChecklistToSplit(
|
|
|
|
|
if (routingDetailId) {
|
|
|
|
|
const result = await client.query(
|
|
|
|
|
`INSERT INTO process_work_result (
|
|
|
|
|
company_code, work_order_process_id,
|
|
|
|
|
id, company_code, work_order_process_id,
|
|
|
|
|
source_work_item_id, source_detail_id,
|
|
|
|
|
work_phase, item_title, item_sort_order,
|
|
|
|
|
detail_content, detail_type, detail_sort_order, is_required,
|
|
|
|
@@ -38,7 +38,7 @@ async function copyChecklistToSplit(
|
|
|
|
|
status, writer
|
|
|
|
|
)
|
|
|
|
|
SELECT
|
|
|
|
|
pwi.company_code, $1,
|
|
|
|
|
gen_random_uuid()::text, pwi.company_code, $1,
|
|
|
|
|
pwi.id, pwd.id,
|
|
|
|
|
pwi.work_phase, pwi.title, pwi.sort_order::text,
|
|
|
|
|
pwd.content, pwd.detail_type, pwd.sort_order::text, pwd.is_required,
|
|
|
|
@@ -59,7 +59,7 @@ async function copyChecklistToSplit(
|
|
|
|
|
// B. routing_detail_id가 없으면 마스터 행의 process_work_result에서 구조만 복사 (타이머/결과값 초기화)
|
|
|
|
|
const result = await client.query(
|
|
|
|
|
`INSERT INTO process_work_result (
|
|
|
|
|
company_code, work_order_process_id,
|
|
|
|
|
id, company_code, work_order_process_id,
|
|
|
|
|
source_work_item_id, source_detail_id,
|
|
|
|
|
work_phase, item_title, item_sort_order,
|
|
|
|
|
detail_content, detail_type, detail_sort_order, is_required,
|
|
|
|
@@ -68,7 +68,7 @@ async function copyChecklistToSplit(
|
|
|
|
|
status, writer
|
|
|
|
|
)
|
|
|
|
|
SELECT
|
|
|
|
|
company_code, $1,
|
|
|
|
|
gen_random_uuid()::text, company_code, $1,
|
|
|
|
|
source_work_item_id, source_detail_id,
|
|
|
|
|
work_phase, item_title, item_sort_order,
|
|
|
|
|
detail_content, detail_type, detail_sort_order, is_required,
|
|
|
|
@@ -168,10 +168,10 @@ export const createWorkProcesses = async (
|
|
|
|
|
// 2. work_order_process INSERT
|
|
|
|
|
const wopResult = await client.query(
|
|
|
|
|
`INSERT INTO work_order_process (
|
|
|
|
|
company_code, wo_id, seq_no, process_code, process_name,
|
|
|
|
|
id, company_code, wo_id, seq_no, process_code, process_name,
|
|
|
|
|
is_required, is_fixed_order, standard_time, plan_qty,
|
|
|
|
|
status, routing_detail_id, writer
|
|
|
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
|
|
|
) VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
|
|
|
RETURNING id`,
|
|
|
|
|
[
|
|
|
|
|
companyCode,
|
|
|
|
@@ -778,13 +778,13 @@ export const saveResult = async (
|
|
|
|
|
const masterId = proc.parent_process_id || work_order_process_id;
|
|
|
|
|
const reworkInsert = await pool.query(
|
|
|
|
|
`INSERT INTO work_order_process (
|
|
|
|
|
wo_id, seq_no, process_code, process_name, is_required, is_fixed_order,
|
|
|
|
|
id, wo_id, seq_no, process_code, process_name, is_required, is_fixed_order,
|
|
|
|
|
standard_time, equipment_code, routing_detail_id,
|
|
|
|
|
status, input_qty, good_qty, defect_qty, concession_qty, total_production_qty,
|
|
|
|
|
result_status, is_rework, rework_source_id,
|
|
|
|
|
parent_process_id, company_code, writer
|
|
|
|
|
) VALUES (
|
|
|
|
|
$1, $2, $3, $4, $5, $6, $7, $8, $9,
|
|
|
|
|
gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9,
|
|
|
|
|
'acceptable', $10, '0', '0', '0', '0',
|
|
|
|
|
'draft', 'Y', $11,
|
|
|
|
|
$12, $13, $14
|
|
|
|
@@ -1444,13 +1444,13 @@ export const acceptProcess = async (req: AuthenticatedRequest, res: Response) =>
|
|
|
|
|
// 분할 행 INSERT (원본 행에서 공정 정보 복사)
|
|
|
|
|
const result = await pool.query(
|
|
|
|
|
`INSERT INTO work_order_process (
|
|
|
|
|
wo_id, seq_no, process_code, process_name, is_required, is_fixed_order,
|
|
|
|
|
id, wo_id, seq_no, process_code, process_name, is_required, is_fixed_order,
|
|
|
|
|
standard_time, equipment_code, routing_detail_id,
|
|
|
|
|
status, input_qty, good_qty, defect_qty, total_production_qty,
|
|
|
|
|
result_status, accepted_by, accepted_at, started_at,
|
|
|
|
|
parent_process_id, company_code, writer
|
|
|
|
|
) VALUES (
|
|
|
|
|
$1, $2, $3, $4, $5, $6, $7, $8, $9,
|
|
|
|
|
gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9,
|
|
|
|
|
'in_progress', $10, '0', '0', '0',
|
|
|
|
|
'draft', $11, NOW()::text, NOW()::text,
|
|
|
|
|
$12, $13, $11
|
|
|
|
@@ -1607,3 +1607,355 @@ export const cancelAccept = async (
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
// POP 전용 함수 (PC 코드와 분리)
|
|
|
|
|
// ========================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 내부 헬퍼: 단일 작업지시에 대해 work_order_process + process_work_result 일괄 생성
|
|
|
|
|
* syncWorkInstructions에서 사용한다.
|
|
|
|
|
*/
|
|
|
|
|
async function generateWorkProcessesForInstruction(
|
|
|
|
|
client: { query: (text: string, values?: any[]) => Promise<any> },
|
|
|
|
|
workInstructionId: string,
|
|
|
|
|
routingVersionId: string,
|
|
|
|
|
planQty: string | null,
|
|
|
|
|
companyCode: string,
|
|
|
|
|
userId: string
|
|
|
|
|
): Promise<{
|
|
|
|
|
processes: Array<{ id: string; seq_no: string; process_name: string; checklist_count: number }>;
|
|
|
|
|
total_checklists: number;
|
|
|
|
|
} | null> {
|
|
|
|
|
const existCheck = await client.query(
|
|
|
|
|
`SELECT COUNT(*) as cnt FROM work_order_process
|
|
|
|
|
WHERE wo_id = $1 AND company_code = $2`,
|
|
|
|
|
[workInstructionId, companyCode]
|
|
|
|
|
);
|
|
|
|
|
if (parseInt(existCheck.rows[0].cnt, 10) > 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const routingDetails = await client.query(
|
|
|
|
|
`SELECT rd.id, rd.seq_no, rd.process_code,
|
|
|
|
|
COALESCE(pm.process_name, rd.process_code) as process_name,
|
|
|
|
|
rd.is_required, rd.is_fixed_order, rd.standard_time
|
|
|
|
|
FROM item_routing_detail rd
|
|
|
|
|
LEFT JOIN process_mng pm ON pm.process_code = rd.process_code
|
|
|
|
|
AND pm.company_code = rd.company_code
|
|
|
|
|
WHERE rd.routing_version_id = $1 AND rd.company_code = $2
|
|
|
|
|
ORDER BY CAST(rd.seq_no AS int) NULLS LAST`,
|
|
|
|
|
[routingVersionId, companyCode]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (routingDetails.rows.length === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const processes: Array<{
|
|
|
|
|
id: string; seq_no: string; process_name: string; checklist_count: number;
|
|
|
|
|
}> = [];
|
|
|
|
|
let totalChecklists = 0;
|
|
|
|
|
|
|
|
|
|
for (const rd of routingDetails.rows) {
|
|
|
|
|
const wopResult = await client.query(
|
|
|
|
|
`INSERT INTO work_order_process (
|
|
|
|
|
id, company_code, wo_id, seq_no, process_code, process_name,
|
|
|
|
|
is_required, is_fixed_order, standard_time, plan_qty,
|
|
|
|
|
status, routing_detail_id, writer
|
|
|
|
|
) VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
|
|
|
RETURNING id`,
|
|
|
|
|
[
|
|
|
|
|
companyCode, workInstructionId, rd.seq_no, rd.process_code, rd.process_name,
|
|
|
|
|
rd.is_required, rd.is_fixed_order, rd.standard_time, planQty || null,
|
|
|
|
|
parseInt(rd.seq_no, 10) === 1 ? "acceptable" : "waiting", rd.id, userId,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
const wopId = wopResult.rows[0].id;
|
|
|
|
|
|
|
|
|
|
const checklistCount = await copyChecklistToSplit(
|
|
|
|
|
client, wopId, wopId, rd.id, companyCode, userId
|
|
|
|
|
);
|
|
|
|
|
totalChecklists += checklistCount;
|
|
|
|
|
|
|
|
|
|
processes.push({
|
|
|
|
|
id: wopId, seq_no: rd.seq_no, process_name: rd.process_name, checklist_count: checklistCount,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { processes, total_checklists: totalChecklists };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POP: 미동기화 작업지시 일괄 동기화
|
|
|
|
|
*/
|
|
|
|
|
export const syncWorkInstructions = async (
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
const pool = getPool();
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
|
|
|
|
const unsyncedResult = await pool.query(
|
|
|
|
|
`SELECT wi.id, wi.work_instruction_no, wi.routing, wi.qty
|
|
|
|
|
FROM work_instruction wi
|
|
|
|
|
WHERE wi.company_code = $1
|
|
|
|
|
AND wi.routing IS NOT NULL
|
|
|
|
|
AND NOT EXISTS (
|
|
|
|
|
SELECT 1 FROM work_order_process wop
|
|
|
|
|
WHERE wop.wo_id = wi.id AND wop.company_code = $1
|
|
|
|
|
)`,
|
|
|
|
|
[companyCode]
|
|
|
|
|
);
|
|
|
|
|
const unsynced = unsyncedResult.rows;
|
|
|
|
|
if (unsynced.length === 0) {
|
|
|
|
|
return res.json({ success: true, data: { synced: 0, skipped: 0, errors: 0, details: [] } });
|
|
|
|
|
}
|
|
|
|
|
let synced = 0, skipped = 0, errors = 0;
|
|
|
|
|
const details: Array<{
|
|
|
|
|
work_instruction_id: string; work_instruction_no: string;
|
|
|
|
|
status: "synced" | "skipped" | "error"; process_count?: number; error?: string;
|
|
|
|
|
}> = [];
|
|
|
|
|
for (const wi of unsynced) {
|
|
|
|
|
const client = await pool.connect();
|
|
|
|
|
try {
|
|
|
|
|
await client.query("BEGIN");
|
|
|
|
|
const result = await generateWorkProcessesForInstruction(
|
|
|
|
|
client, wi.id, wi.routing, wi.qty || null, companyCode, userId
|
|
|
|
|
);
|
|
|
|
|
if (!result) {
|
|
|
|
|
await client.query("ROLLBACK");
|
|
|
|
|
skipped++;
|
|
|
|
|
details.push({ work_instruction_id: wi.id, work_instruction_no: wi.work_instruction_no, status: "skipped" });
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
await client.query("COMMIT");
|
|
|
|
|
synced++;
|
|
|
|
|
details.push({
|
|
|
|
|
work_instruction_id: wi.id, work_instruction_no: wi.work_instruction_no,
|
|
|
|
|
status: "synced", process_count: result.processes.length,
|
|
|
|
|
});
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
await client.query("ROLLBACK");
|
|
|
|
|
errors++;
|
|
|
|
|
details.push({
|
|
|
|
|
work_instruction_id: wi.id, work_instruction_no: wi.work_instruction_no,
|
|
|
|
|
status: "error", error: err.message || "알 수 없는 오류",
|
|
|
|
|
});
|
|
|
|
|
} finally {
|
|
|
|
|
client.release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return res.json({ success: true, data: { synced, skipped, errors, details } });
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error("[pop/production] sync-work-instructions 오류:", error);
|
|
|
|
|
return res.status(500).json({ success: false, message: error.message || "동기화 오류" });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getWarehouses = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
const pool = getPool();
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const result = await pool.query(
|
|
|
|
|
`SELECT id, warehouse_code, warehouse_name, warehouse_type
|
|
|
|
|
FROM warehouse_info WHERE company_code = $1 AND COALESCE(status, '') != '삭제' ORDER BY warehouse_name`,
|
|
|
|
|
[companyCode]
|
|
|
|
|
);
|
|
|
|
|
return res.json({ success: true, data: result.rows });
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getWarehouseLocations = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
const pool = getPool();
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const { warehouseId } = req.params;
|
|
|
|
|
if (!warehouseId) return res.status(400).json({ success: false, message: "warehouseId 필수" });
|
|
|
|
|
const whInfo = await pool.query(
|
|
|
|
|
`SELECT warehouse_code FROM warehouse_info WHERE id = $1 AND company_code = $2`,
|
|
|
|
|
[warehouseId, companyCode]
|
|
|
|
|
);
|
|
|
|
|
if (whInfo.rowCount === 0) return res.json({ success: true, data: [] });
|
|
|
|
|
const result = await pool.query(
|
|
|
|
|
`SELECT id, location_code, location_name FROM warehouse_location
|
|
|
|
|
WHERE warehouse_code = $1 AND company_code = $2 ORDER BY location_name`,
|
|
|
|
|
[whInfo.rows[0].warehouse_code, companyCode]
|
|
|
|
|
);
|
|
|
|
|
return res.json({ success: true, data: result.rows });
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const isLastProcess = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
const pool = getPool();
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const { processId } = req.params;
|
|
|
|
|
if (!processId) return res.json({ success: true, data: { isLast: false } });
|
|
|
|
|
const process = await pool.query(
|
|
|
|
|
`SELECT wo_id, seq_no, parent_process_id FROM work_order_process WHERE id = $1 AND company_code = $2`,
|
|
|
|
|
[processId, companyCode]
|
|
|
|
|
);
|
|
|
|
|
if (process.rowCount === 0) return res.json({ success: true, data: { isLast: false } });
|
|
|
|
|
const { wo_id, seq_no, parent_process_id } = process.rows[0];
|
|
|
|
|
let effectiveSeqNo = seq_no;
|
|
|
|
|
if (parent_process_id) {
|
|
|
|
|
const master = await pool.query(
|
|
|
|
|
`SELECT seq_no FROM work_order_process WHERE id = $1 AND company_code = $2`,
|
|
|
|
|
[parent_process_id, companyCode]
|
|
|
|
|
);
|
|
|
|
|
if (master.rowCount > 0) effectiveSeqNo = master.rows[0].seq_no;
|
|
|
|
|
}
|
|
|
|
|
const next = await pool.query(
|
|
|
|
|
`SELECT id FROM work_order_process
|
|
|
|
|
WHERE wo_id = $1 AND company_code = $2 AND CAST(seq_no AS int) > CAST($3 AS int) AND parent_process_id IS NULL LIMIT 1`,
|
|
|
|
|
[wo_id, companyCode, effectiveSeqNo]
|
|
|
|
|
);
|
|
|
|
|
const warehouseInfo = await pool.query(
|
|
|
|
|
`SELECT target_warehouse_id, target_location_code FROM work_order_process WHERE id = $1 AND company_code = $2`,
|
|
|
|
|
[processId, companyCode]
|
|
|
|
|
);
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
isLast: next.rowCount === 0, woId: wo_id, seqNo: effectiveSeqNo,
|
|
|
|
|
targetWarehouseId: warehouseInfo.rows[0]?.target_warehouse_id || null,
|
|
|
|
|
targetLocationCode: warehouseInfo.rows[0]?.target_location_code || null,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const updateTargetWarehouse = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
const pool = getPool();
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
|
|
|
|
const { work_order_process_id, target_warehouse_id, target_location_code } = req.body;
|
|
|
|
|
if (!work_order_process_id || !target_warehouse_id)
|
|
|
|
|
return res.status(400).json({ success: false, message: "work_order_process_id와 target_warehouse_id 필수" });
|
|
|
|
|
const procInfo = await pool.query(
|
|
|
|
|
`SELECT parent_process_id FROM work_order_process WHERE id = $1 AND company_code = $2`,
|
|
|
|
|
[work_order_process_id, companyCode]
|
|
|
|
|
);
|
|
|
|
|
const idsToUpdate = [work_order_process_id];
|
|
|
|
|
if (procInfo.rowCount > 0 && procInfo.rows[0].parent_process_id) idsToUpdate.push(procInfo.rows[0].parent_process_id);
|
|
|
|
|
for (const id of idsToUpdate) {
|
|
|
|
|
await pool.query(
|
|
|
|
|
`UPDATE work_order_process SET target_warehouse_id = $3, target_location_code = $4, writer = $5, updated_date = NOW()
|
|
|
|
|
WHERE id = $1 AND company_code = $2`,
|
|
|
|
|
[id, companyCode, target_warehouse_id, target_location_code || null, userId]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return res.json({ success: true, data: { target_warehouse_id, target_location_code } });
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const inventoryInbound = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
const pool = getPool();
|
|
|
|
|
const client = await pool.connect();
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
|
|
|
|
const { work_order_process_id, warehouse_code, location_code } = req.body;
|
|
|
|
|
if (!work_order_process_id || !warehouse_code)
|
|
|
|
|
return res.status(400).json({ success: false, message: "work_order_process_id와 warehouse_code 필수" });
|
|
|
|
|
await client.query("BEGIN");
|
|
|
|
|
const procResult = await client.query(
|
|
|
|
|
`SELECT wo_id, good_qty, concession_qty, parent_process_id, target_warehouse_id FROM work_order_process WHERE id = $1 AND company_code = $2`,
|
|
|
|
|
[work_order_process_id, companyCode]
|
|
|
|
|
);
|
|
|
|
|
if (procResult.rowCount === 0) { await client.query("ROLLBACK"); return res.status(404).json({ success: false, message: "공정 없음" }); }
|
|
|
|
|
const proc = procResult.rows[0];
|
|
|
|
|
if (proc.target_warehouse_id) { await client.query("ROLLBACK"); return res.status(409).json({ success: false, message: "이미 입고 완료" }); }
|
|
|
|
|
const goodQty = parseInt(proc.good_qty || "0", 10) + parseInt(proc.concession_qty || "0", 10);
|
|
|
|
|
if (goodQty <= 0) { await client.query("ROLLBACK"); return res.status(400).json({ success: false, message: "양품 0" }); }
|
|
|
|
|
const wiResult = await client.query(`SELECT item_id FROM work_instruction WHERE id = $1 AND company_code = $2`, [proc.wo_id, companyCode]);
|
|
|
|
|
if (wiResult.rowCount === 0) { await client.query("ROLLBACK"); return res.status(404).json({ success: false, message: "작업지시 없음" }); }
|
|
|
|
|
const itemResult = await client.query(`SELECT item_number FROM item_info WHERE id = $1 AND company_code = $2`, [wiResult.rows[0].item_id, companyCode]);
|
|
|
|
|
if (itemResult.rowCount === 0) { await client.query("ROLLBACK"); return res.status(404).json({ success: false, message: "품목 없음" }); }
|
|
|
|
|
const itemCode = itemResult.rows[0].item_number;
|
|
|
|
|
const locCode = location_code || warehouse_code;
|
|
|
|
|
await client.query(
|
|
|
|
|
`INSERT INTO inventory_stock (id, company_code, item_code, warehouse_code, location_code, current_qty, last_in_date, created_date, updated_date, writer)
|
|
|
|
|
VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, NOW(), NOW(), NOW(), $6)
|
|
|
|
|
ON CONFLICT (company_code, item_code, warehouse_code, location_code)
|
|
|
|
|
DO UPDATE SET current_qty = (COALESCE(inventory_stock.current_qty::numeric, 0) + $5::numeric)::text,
|
|
|
|
|
last_in_date = NOW(), updated_date = NOW(), writer = $6`,
|
|
|
|
|
[companyCode, itemCode, warehouse_code, locCode, String(goodQty), userId]
|
|
|
|
|
);
|
|
|
|
|
const idsToUpdate = [work_order_process_id];
|
|
|
|
|
if (proc.parent_process_id) idsToUpdate.push(proc.parent_process_id);
|
|
|
|
|
for (const id of idsToUpdate) {
|
|
|
|
|
await client.query(
|
|
|
|
|
`UPDATE work_order_process SET target_warehouse_id = $3, target_location_code = $4, writer = $5, updated_date = NOW() WHERE id = $1 AND company_code = $2`,
|
|
|
|
|
[id, companyCode, warehouse_code, location_code || null, userId]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
await client.query("COMMIT");
|
|
|
|
|
return res.json({ success: true, message: "재고 입고 완료", data: { item_code: itemCode, warehouse_code, location_code: locCode, qty: goodQty } });
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
await client.query("ROLLBACK").catch(() => {});
|
|
|
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
|
|
|
} finally { client.release(); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const quickInventoryInbound = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
const pool = getPool();
|
|
|
|
|
const client = await pool.connect();
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
|
|
|
|
const { item_id, qty, warehouse_code, location_code, remark } = req.body;
|
|
|
|
|
if (!item_id || !qty || !warehouse_code)
|
|
|
|
|
return res.status(400).json({ success: false, message: "item_id, qty, warehouse_code 필수" });
|
|
|
|
|
const parsedQty = parseInt(String(qty), 10);
|
|
|
|
|
if (isNaN(parsedQty) || parsedQty <= 0)
|
|
|
|
|
return res.status(400).json({ success: false, message: "수량은 1 이상" });
|
|
|
|
|
await client.query("BEGIN");
|
|
|
|
|
const itemResult = await client.query(
|
|
|
|
|
`SELECT item_number, item_name, size, material, unit FROM item_info WHERE id = $1 AND company_code = $2`, [item_id, companyCode]
|
|
|
|
|
);
|
|
|
|
|
if (itemResult.rowCount === 0) { await client.query("ROLLBACK"); return res.status(404).json({ success: false, message: "품목 없음" }); }
|
|
|
|
|
const item = itemResult.rows[0];
|
|
|
|
|
const locCode = location_code || warehouse_code;
|
|
|
|
|
await client.query(
|
|
|
|
|
`INSERT INTO inventory_stock (id, company_code, item_code, warehouse_code, location_code, current_qty, last_in_date, created_date, updated_date, writer)
|
|
|
|
|
VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, NOW(), NOW(), NOW(), $6)
|
|
|
|
|
ON CONFLICT (company_code, item_code, warehouse_code, location_code)
|
|
|
|
|
DO UPDATE SET current_qty = (COALESCE(inventory_stock.current_qty::numeric, 0) + $5::numeric)::text,
|
|
|
|
|
last_in_date = NOW(), updated_date = NOW(), writer = $6`,
|
|
|
|
|
[companyCode, item.item_number, warehouse_code, locCode, String(parsedQty), userId]
|
|
|
|
|
);
|
|
|
|
|
const seqResult = await client.query(
|
|
|
|
|
`SELECT COALESCE(MAX(CASE WHEN inbound_number ~ '^QIB-[0-9]{4}-[0-9]+$'
|
|
|
|
|
THEN CAST(SUBSTRING(inbound_number FROM '[0-9]+$') AS INTEGER) ELSE 0 END), 0) + 1 AS next_seq
|
|
|
|
|
FROM inbound_mng WHERE company_code = $1`, [companyCode]
|
|
|
|
|
);
|
|
|
|
|
const inboundNumber = `QIB-${new Date().getFullYear()}-${String(seqResult.rows[0].next_seq).padStart(4, "0")}`;
|
|
|
|
|
await client.query(
|
|
|
|
|
`INSERT INTO inbound_mng (id, company_code, inbound_number, inbound_type, inbound_date,
|
|
|
|
|
item_number, item_name, spec, material, unit, inbound_qty, warehouse_code, location_code,
|
|
|
|
|
inbound_status, memo, remark, created_date, updated_date, writer, created_by, updated_by
|
|
|
|
|
) VALUES (gen_random_uuid()::text, $1, $2, '간이입고', CURRENT_DATE,
|
|
|
|
|
$3, $4, $5, $6, $7, $8, $9, $10, '완료', $11, $12, NOW(), NOW(), $13, $13, $13)`,
|
|
|
|
|
[companyCode, inboundNumber, item.item_number, item.item_name, item.size, item.material, item.unit,
|
|
|
|
|
parsedQty, warehouse_code, locCode, remark || "POP 간이입고", remark || null, userId]
|
|
|
|
|
);
|
|
|
|
|
await client.query("COMMIT");
|
|
|
|
|
return res.json({ success: true, message: "간이 입고 완료",
|
|
|
|
|
data: { inbound_number: inboundNumber, item_code: item.item_number, item_name: item.item_name, warehouse_code, location_code: locCode, qty: parsedQty } });
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
await client.query("ROLLBACK").catch(() => {});
|
|
|
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
|
|
|
} finally { client.release(); }
|
|
|
|
|
};
|
|
|
|
|