import { Request, Response } from "express"; import { getPool } from "../database/db"; import { logger } from "../utils/logger"; /** * 수주 번호 생성 함수 * 형식: ORD + YYMMDD + 4자리 시퀀스 * 예: ORD250114001 */ async function generateOrderNumber(companyCode: string): Promise { const pool = getPool(); const today = new Date(); const year = today.getFullYear().toString().slice(2); // 25 const month = String(today.getMonth() + 1).padStart(2, "0"); // 01 const day = String(today.getDate()).padStart(2, "0"); // 14 const dateStr = `${year}${month}${day}`; // 250114 // 당일 수주 카운트 조회 const countQuery = ` SELECT COUNT(*) as count FROM order_mng_master WHERE objid LIKE $1 AND writer LIKE $2 `; const pattern = `ORD${dateStr}%`; const result = await pool.query(countQuery, [pattern, `%${companyCode}%`]); const count = parseInt(result.rows[0]?.count || "0"); const seq = count + 1; return `ORD${dateStr}${String(seq).padStart(4, "0")}`; // ORD250114001 } /** * 수주 등록 API * POST /api/orders */ export async function createOrder(req: Request, res: Response) { const pool = getPool(); try { const { inputMode, // 입력 방식 customerCode, // 거래처 코드 deliveryDate, // 납품일 items, // 품목 목록 memo, // 메모 } = req.body; // 멀티테넌시 const companyCode = req.user!.companyCode; const userId = req.user!.userId; // 유효성 검사 if (!customerCode) { return res.status(400).json({ success: false, message: "거래처 코드는 필수입니다", }); } if (!items || items.length === 0) { return res.status(400).json({ success: false, message: "품목은 최소 1개 이상 필요합니다", }); } // 수주 번호 생성 const orderNo = await generateOrderNumber(companyCode); // 전체 금액 계산 const totalAmount = items.reduce( (sum: number, item: any) => sum + (item.amount || 0), 0 ); // 수주 마스터 생성 const masterQuery = ` INSERT INTO order_mng_master ( objid, partner_objid, final_delivery_date, reason, status, reg_date, writer ) VALUES ($1, $2, $3, $4, $5, NOW(), $6) RETURNING * `; const masterResult = await pool.query(masterQuery, [ orderNo, customerCode, deliveryDate || null, memo || null, "진행중", `${userId}|${companyCode}`, ]); const masterObjid = masterResult.rows[0].objid; // 수주 상세 (품목) 생성 for (let i = 0; i < items.length; i++) { const item = items[i]; const subObjid = `${orderNo}_${i + 1}`; const subQuery = ` INSERT INTO order_mng_sub ( objid, order_mng_master_objid, part_objid, partner_objid, partner_price, partner_qty, delivery_date, status, regdate, writer ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), $9) `; await pool.query(subQuery, [ subObjid, masterObjid, item.item_code || item.id, // 품목 코드 customerCode, item.unit_price || 0, item.quantity || 0, item.delivery_date || deliveryDate || null, "진행중", `${userId}|${companyCode}`, ]); } logger.info("수주 등록 성공", { companyCode, orderNo, masterObjid, itemCount: items.length, totalAmount, }); res.json({ success: true, data: { orderNo, masterObjid, itemCount: items.length, totalAmount, }, message: "수주가 등록되었습니다", }); } catch (error: any) { logger.error("수주 등록 오류", { error: error.message, stack: error.stack, }); res.status(500).json({ success: false, message: error.message || "수주 등록 중 오류가 발생했습니다", }); } } /** * 수주 목록 조회 API * GET /api/orders */ export async function getOrders(req: Request, res: Response) { const pool = getPool(); try { const { page = "1", limit = "20", searchText = "" } = req.query; const companyCode = req.user!.companyCode; const offset = (parseInt(page as string) - 1) * parseInt(limit as string); // WHERE 조건 const whereConditions: string[] = []; const params: any[] = []; let paramIndex = 1; // 멀티테넌시 (writer 필드에 company_code 포함) if (companyCode !== "*") { whereConditions.push(`writer LIKE $${paramIndex}`); params.push(`%${companyCode}%`); paramIndex++; } // 검색 if (searchText) { whereConditions.push(`objid LIKE $${paramIndex}`); params.push(`%${searchText}%`); paramIndex++; } const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : ""; // 카운트 쿼리 const countQuery = `SELECT COUNT(*) as count FROM order_mng_master ${whereClause}`; const countResult = await pool.query(countQuery, params); const total = parseInt(countResult.rows[0]?.count || "0"); // 데이터 쿼리 const dataQuery = ` SELECT * FROM order_mng_master ${whereClause} ORDER BY reg_date DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1} `; params.push(parseInt(limit as string)); params.push(offset); const dataResult = await pool.query(dataQuery, params); res.json({ success: true, data: dataResult.rows, pagination: { total, page: parseInt(page as string), limit: parseInt(limit as string), }, }); } catch (error: any) { logger.error("수주 목록 조회 오류", { error: error.message }); res.status(500).json({ success: false, message: error.message, }); } }