From 147f910c88d984750a9f4936afc7c1d7a09d6d0c Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Fri, 21 Nov 2025 12:17:29 +0900 Subject: [PATCH] =?UTF-8?q?refactor(order):=20=EB=B0=B1=EC=97=94=EB=93=9C?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0,=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20UI=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 백엔드: - order_mng_master + order_mng_sub LEFT JOIN 조회 - json_agg로 품목 배열화 (items: OrderItem[]) - 타입 정의 추가 (order.ts) 프론트엔드: - 미사용 그룹화 UI 컴포넌트 삭제 - 평면 테이블 형태 유지 (기존 UI 그대로) Modified: - backend-node/src/controllers/orderController.ts --- .../src/controllers/orderController.ts | 54 +++++++++++-- backend-node/src/types/order.ts | 80 +++++++++++++++++++ 2 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 backend-node/src/types/order.ts 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; // 전체 금액 +} +