diff --git a/backend-node/src/controllers/orderController.ts b/backend-node/src/controllers/orderController.ts index 0b76fd95..6c4d7007 100644 --- a/backend-node/src/controllers/orderController.ts +++ b/backend-node/src/controllers/orderController.ts @@ -164,7 +164,7 @@ export async function createOrder(req: Request, res: Response) { } /** - * 수주 목록 조회 API + * 수주 목록 조회 API (마스터 + 품목 JOIN) * GET /api/orders */ export async function getOrders(req: Request, res: Response) { @@ -183,14 +183,14 @@ export async function getOrders(req: Request, res: Response) { // 멀티테넌시 (writer 필드에 company_code 포함) if (companyCode !== "*") { - whereConditions.push(`writer LIKE $${paramIndex}`); + whereConditions.push(`m.writer LIKE $${paramIndex}`); params.push(`%${companyCode}%`); paramIndex++; } // 검색 if (searchText) { - whereConditions.push(`objid LIKE $${paramIndex}`); + whereConditions.push(`m.objid LIKE $${paramIndex}`); params.push(`%${searchText}%`); paramIndex++; } @@ -200,16 +200,47 @@ export async function getOrders(req: Request, res: Response) { ? `WHERE ${whereConditions.join(" AND ")}` : ""; - // 카운트 쿼리 - const countQuery = `SELECT COUNT(*) as count FROM order_mng_master ${whereClause}`; + // 카운트 쿼리 (고유한 수주 개수) + const countQuery = ` + SELECT COUNT(DISTINCT m.objid) as count + FROM order_mng_master m + ${whereClause} + `; const countResult = await pool.query(countQuery, params); const total = parseInt(countResult.rows[0]?.count || "0"); - // 데이터 쿼리 + // 데이터 쿼리 (마스터 + 품목 JOIN) const dataQuery = ` - SELECT * FROM order_mng_master + SELECT + m.objid as order_no, + m.partner_objid, + m.final_delivery_date, + m.reason, + m.status, + m.reg_date, + m.writer, + COALESCE( + json_agg( + CASE WHEN s.objid IS NOT NULL THEN + json_build_object( + 'sub_objid', s.objid, + 'part_objid', s.part_objid, + 'partner_price', s.partner_price, + 'partner_qty', s.partner_qty, + 'delivery_date', s.delivery_date, + 'status', s.status, + 'regdate', s.regdate + ) + END + ORDER BY s.regdate + ) FILTER (WHERE s.objid IS NOT NULL), + '[]'::json + ) as items + FROM order_mng_master m + LEFT JOIN order_mng_sub s ON m.objid = s.order_mng_master_objid ${whereClause} - ORDER BY reg_date DESC + GROUP BY m.objid, m.partner_objid, m.final_delivery_date, m.reason, m.status, m.reg_date, m.writer + ORDER BY m.reg_date DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1} `; @@ -218,6 +249,13 @@ export async function getOrders(req: Request, res: Response) { const dataResult = await pool.query(dataQuery, params); + logger.info("수주 목록 조회 성공", { + companyCode, + total, + page: parseInt(page as string), + itemCount: dataResult.rows.length, + }); + res.json({ success: true, data: dataResult.rows, diff --git a/backend-node/src/types/order.ts b/backend-node/src/types/order.ts new file mode 100644 index 00000000..30731b86 --- /dev/null +++ b/backend-node/src/types/order.ts @@ -0,0 +1,80 @@ +/** + * 수주 관리 타입 정의 + */ + +/** + * 수주 품목 (order_mng_sub) + */ +export interface OrderItem { + sub_objid: string; // 품목 고유 ID (예: ORD-20251121-051_1) + part_objid: string; // 품목 코드 + partner_price: number; // 단가 + partner_qty: number; // 수량 + delivery_date: string | null; // 납기일 + status: string; // 상태 + regdate: string; // 등록일 +} + +/** + * 수주 마스터 (order_mng_master) + */ +export interface OrderMaster { + order_no: string; // 수주 번호 (예: ORD-20251121-051) + partner_objid: string; // 거래처 코드 + final_delivery_date: string | null; // 최종 납품일 + reason: string | null; // 메모/사유 + status: string; // 상태 + reg_date: string; // 등록일 + writer: string; // 작성자 (userId|companyCode) +} + +/** + * 수주 + 품목 (API 응답) + */ +export interface OrderWithItems extends OrderMaster { + items: OrderItem[]; // 품목 목록 +} + +/** + * 수주 등록 요청 + */ +export interface CreateOrderRequest { + inputMode: string; // 입력 방식 + salesType?: string; // 판매 유형 (국내/해외) + priceType?: string; // 단가 방식 + customerCode: string; // 거래처 코드 + contactPerson?: string; // 담당자 + deliveryDestination?: string; // 납품처 + deliveryAddress?: string; // 납품장소 + deliveryDate?: string; // 납품일 + items: Array<{ + item_code?: string; // 품목 코드 + id?: string; // 품목 ID (item_code 대체) + quantity?: number; // 수량 + unit_price?: number; // 단가 + selling_price?: number; // 판매가 + amount?: number; // 금액 + delivery_date?: string; // 품목별 납기일 + }>; + memo?: string; // 메모 + tradeInfo?: { + // 해외 판매 시 + incoterms?: string; + paymentTerms?: string; + currency?: string; + portOfLoading?: string; + portOfDischarge?: string; + hsCode?: string; + }; +} + +/** + * 수주 등록 응답 + */ +export interface CreateOrderResponse { + orderNo: string; // 생성된 수주 번호 + masterObjid: string; // 마스터 ID + itemCount: number; // 품목 개수 + totalAmount: number; // 전체 금액 +} +