Files
vexplor/backend-node/src/controllers/popInOutDetailController.ts
kmh 854366453c Bundle pending POP work: production / inventory inout / inbound
진행 중이던 POP 관련 변경사항을 한 번에 묶어 커밋.

- backend
  - popProductionController: 생산공정 처리/접수 로직 대폭 갱신 (+663)
  - receivingController, popInventoryRoutes, adminService 보강
  - popInOutDetailController / popInOutHistoryController 신규
- frontend (POP)
  - 생산 화면 (DefectTypeModal / ProcessWork / WorkOrderList / main page)
    COMPANY_7/8/9/10/16/29/30 동기화
  - 입출고 이력·디테일 화면 신규 (inventory/page, inventory/inout-manage,
    InOutDetailModal) 7개사
  - COMPANY_7 입고 화면 (InboundCartPage / ProductionInbound /
    inbound/production/page) 보강
  - COMPANY_7 재고조정 화면 (inventory/adjust) UI 골격 신규
- frontend lib
  - popInOutDetail / popInOutHistory API 클라이언트 신규

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:54:54 +09:00

160 lines
4.7 KiB
TypeScript

/**
* POP 입출고관리 디테일 단건 조회 컨트롤러
*
* inout-history 카드 클릭 시 모달용 단건 조회 API 3종 (read-only).
* 라우트는 popInventoryRoutes.ts 에서 source 별로 분리:
* GET /api/pop/inventory/inout-detail/inbound/:id (+ ?detail_id=...)
* GET /api/pop/inventory/inout-detail/outbound/:id
* GET /api/pop/inventory/inout-detail/history/:id
*/
import type { Response } from "express";
import { getPool } from "../database/db";
import type { AuthenticatedRequest } from "../types/auth";
import { logger } from "../utils/logger";
// ===== inbound: inbound_mng (헤더) + inbound_detail (1행, optional) =====
export async function getInboundDetail(
req: AuthenticatedRequest,
res: Response,
) {
try {
const companyCode = req.user!.companyCode;
const { id } = req.params;
const { detail_id } = req.query as Record<string, string | undefined>;
const pool = getPool();
const headerSql = `
SELECT
im.*,
wh.warehouse_name,
u.user_name AS writer_name
FROM inbound_mng im
LEFT JOIN warehouse_info wh
ON wh.warehouse_code = im.warehouse_code
AND wh.company_code = im.company_code
LEFT JOIN user_info u
ON u.user_id = im.writer
AND u.company_code = im.company_code
WHERE im.id = $1
AND ($2::text = '*' OR im.company_code = $2)
`;
const headerResult = await pool.query(headerSql, [id, companyCode]);
if (headerResult.rowCount === 0) {
return res
.status(404)
.json({ success: false, message: "입고 데이터를 찾을 수 없습니다." });
}
const header = headerResult.rows[0];
let detail: Record<string, unknown> | null = null;
if (detail_id) {
const detailResult = await pool.query(
`SELECT * FROM inbound_detail
WHERE id = $1
AND ($2::text = '*' OR company_code = $2)`,
[detail_id, companyCode],
);
detail = detailResult.rows[0] ?? null;
}
return res.json({ success: true, data: { header, detail } });
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : "조회 실패";
logger.error("입고 디테일 단건 조회 실패", { error: msg });
return res.status(500).json({ success: false, message: msg });
}
}
// ===== outbound: outbound_mng (단건) =====
export async function getOutboundDetail(
req: AuthenticatedRequest,
res: Response,
) {
try {
const companyCode = req.user!.companyCode;
const { id } = req.params;
const pool = getPool();
const sql = `
SELECT
om.*,
wh.warehouse_name,
u.user_name AS writer_name
FROM outbound_mng om
LEFT JOIN warehouse_info wh
ON wh.warehouse_code = om.warehouse_code
AND wh.company_code = om.company_code
LEFT JOIN user_info u
ON u.user_id = om.writer
AND u.company_code = om.company_code
WHERE om.id = $1
AND ($2::text = '*' OR om.company_code = $2)
`;
const result = await pool.query(sql, [id, companyCode]);
if (result.rowCount === 0) {
return res
.status(404)
.json({ success: false, message: "출고 데이터를 찾을 수 없습니다." });
}
return res.json({ success: true, data: { row: result.rows[0] } });
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : "조회 실패";
logger.error("출고 디테일 단건 조회 실패", { error: msg });
return res.status(500).json({ success: false, message: msg });
}
}
// ===== history: inventory_history (단건) + warehouse + item_info JOIN =====
export async function getHistoryDetail(
req: AuthenticatedRequest,
res: Response,
) {
try {
const companyCode = req.user!.companyCode;
const { id } = req.params;
const pool = getPool();
const sql = `
SELECT
ih.*,
wh.warehouse_name,
ii.item_name AS joined_item_name,
ii.unit AS joined_unit
FROM inventory_history ih
LEFT JOIN warehouse_info wh
ON wh.warehouse_code = ih.warehouse_code
AND wh.company_code = ih.company_code
LEFT JOIN (
SELECT DISTINCT ON (item_number, company_code)
item_number, item_name, unit, company_code
FROM item_info
ORDER BY item_number, company_code, created_date DESC
) ii ON ih.item_code = ii.item_number
AND ih.company_code = ii.company_code
WHERE ih.id = $1
AND ($2::text = '*' OR ih.company_code = $2)
`;
const result = await pool.query(sql, [id, companyCode]);
if (result.rowCount === 0) {
return res.status(404).json({
success: false,
message: "재고이력 데이터를 찾을 수 없습니다.",
});
}
return res.json({ success: true, data: { row: result.rows[0] } });
} catch (error: unknown) {
const msg = error instanceof Error ? error.message : "조회 실패";
logger.error("재고이력 디테일 단건 조회 실패", { error: msg });
return res.status(500).json({ success: false, message: msg });
}
}