From ce9516802a88c5e35831815dc1867b65b4dbf018 Mon Sep 17 00:00:00 2001 From: kmh Date: Thu, 30 Apr 2026 10:54:21 +0900 Subject: [PATCH] Bundle POP sales outbound + inspection wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - outbound: wire 판매출고 to shipment_instruction with customer_mng JOIN, add customer filter, auto-transition shipment_instruction.status to COMPLETED/IN_PROGRESS based on remaining qty - inspection: fix is_active filter ('Y' -> '사용') and JOIN inspection_standard to expose judgment_criteria - POP InspectionModal (7 companies): hide measured-value input when judgment_criteria == CAT_JC_03 (O/X) - POP SalesOutbound (7 companies): connect to /outbound/source/shipment-instructions, fix stale-closure bug in saveToDb via ref - COMPANY_16/30 main: temporarily comment out 품질/안전관리 menu entries --- .../src/controllers/outboundController.ts | 45 +++++++- .../src/routes/inspectionResultRoutes.ts | 20 ++-- .../_components/inbound/InspectionModal.tsx | 39 ++++--- .../_components/outbound/SalesOutbound.tsx | 104 ++++++++++++++++-- .../_components/inbound/InspectionModal.tsx | 39 ++++--- .../_components/outbound/SalesOutbound.tsx | 104 ++++++++++++++++-- .../app/(main)/COMPANY_16/pop/main/page.tsx | 4 + .../_components/inbound/InspectionModal.tsx | 39 ++++--- .../_components/outbound/SalesOutbound.tsx | 104 ++++++++++++++++-- .../_components/inbound/InspectionModal.tsx | 39 ++++--- .../_components/outbound/SalesOutbound.tsx | 60 +++++++++- .../app/(main)/COMPANY_30/pop/main/page.tsx | 4 + .../_components/inbound/InspectionModal.tsx | 39 ++++--- .../_components/outbound/SalesOutbound.tsx | 60 +++++++++- .../_components/inbound/InspectionModal.tsx | 39 ++++--- .../_components/outbound/SalesOutbound.tsx | 60 +++++++++- .../_components/inbound/InspectionModal.tsx | 39 ++++--- .../_components/outbound/SalesOutbound.tsx | 60 +++++++++- 18 files changed, 714 insertions(+), 184 deletions(-) diff --git a/backend-node/src/controllers/outboundController.ts b/backend-node/src/controllers/outboundController.ts index 29ec2320..fcdf389b 100644 --- a/backend-node/src/controllers/outboundController.ts +++ b/backend-node/src/controllers/outboundController.ts @@ -180,7 +180,7 @@ export async function create(req: AuthenticatedRequest, res: Response) { item.outbound_status || "대기", manager_id || item.manager_id || null, memo || item.memo || null, - item.source_type || null, + item.source_type || item.source_table || null, item.sales_order_id || null, item.shipment_plan_id || null, item.item_info_id || null, @@ -275,11 +275,12 @@ export async function create(req: AuthenticatedRequest, res: Response) { ); } - // 판매출고인 경우 출하지시의 ship_qty 업데이트 + 수주상세 ship_qty 반영 + // 판매출고인 경우 출하지시의 ship_qty 업데이트 + 수주상세 ship_qty 반영 + master status 자동 전환 + const itemSourceTable = item.source_type || item.source_table; if ( item.outbound_type === "판매출고" && item.source_id && - item.source_type === "shipment_instruction_detail" + itemSourceTable === "shipment_instruction_detail" ) { const outQtyNum = Number(item.outbound_qty) || 0; await client.query( @@ -292,9 +293,12 @@ export async function create(req: AuthenticatedRequest, res: Response) { // 출하지시 상세의 detail_id로 수주상세(sales_order_detail) ship_qty도 업데이트 const sidRes = await client.query( - `SELECT detail_id FROM shipment_instruction_detail WHERE id = $1 AND company_code = $2`, + `SELECT instruction_id, detail_id + FROM shipment_instruction_detail + WHERE id = $1 AND company_code = $2`, [item.source_id, companyCode], ); + const instructionId = sidRes.rows[0]?.instruction_id; const detailId = sidRes.rows[0]?.detail_id; if (detailId) { await client.query( @@ -306,6 +310,27 @@ export async function create(req: AuthenticatedRequest, res: Response) { [outQtyNum, detailId, companyCode], ); } + + // shipment_instruction master status 자동 전환 (입고의 purchase_detail → purchase_order_mng 패턴) + // 모든 detail 의 잔량이 0 이면 COMPLETED, 아니면 IN_PROGRESS + if (instructionId) { + const unshippedRes = await client.query( + `SELECT COUNT(*)::int AS unshipped + FROM shipment_instruction_detail + WHERE instruction_id = $1 AND company_code = $2 + AND COALESCE(plan_qty, 0) > COALESCE(ship_qty, 0)`, + [instructionId, companyCode], + ); + const unshipped = unshippedRes.rows[0]?.unshipped ?? 0; + const newStatus = unshipped === 0 ? "COMPLETED" : "IN_PROGRESS"; + await client.query( + `UPDATE shipment_instruction + SET status = $1, + updated_date = NOW() + WHERE id = $2 AND company_code = $3`, + [newStatus, instructionId, companyCode], + ); + } } } @@ -569,12 +594,18 @@ export async function getShipmentInstructions( ) { try { const companyCode = req.user!.companyCode; - const { keyword } = req.query; + const { keyword, customer } = req.query; const conditions: string[] = ["si.company_code = $1"]; const params: any[] = [companyCode]; let paramIdx = 2; + if (customer) { + conditions.push(`si.partner_id = $${paramIdx}`); + params.push(customer); + paramIdx++; + } + if (keyword) { conditions.push( `(si.instruction_no ILIKE $${paramIdx} OR sid.item_name ILIKE $${paramIdx} OR sid.item_code ILIKE $${paramIdx})`, @@ -591,6 +622,7 @@ export async function getShipmentInstructions( si.instruction_no, si.instruction_date, si.partner_id, + COALESCE(c.customer_name, si.partner_id, '') AS customer_name, si.status AS instruction_status, sid.item_code, sid.item_name, @@ -605,6 +637,9 @@ export async function getShipmentInstructions( JOIN shipment_instruction_detail sid ON si.id = sid.instruction_id AND si.company_code = sid.company_code + LEFT JOIN customer_mng c + ON si.partner_id = c.customer_code + AND si.company_code = c.company_code WHERE ${conditions.join(" AND ")} AND COALESCE(sid.plan_qty, 0) > COALESCE(sid.ship_qty, 0) ORDER BY si.instruction_date DESC, si.instruction_no`, diff --git a/backend-node/src/routes/inspectionResultRoutes.ts b/backend-node/src/routes/inspectionResultRoutes.ts index 1e9d7960..5fbe31a1 100644 --- a/backend-node/src/routes/inspectionResultRoutes.ts +++ b/backend-node/src/routes/inspectionResultRoutes.ts @@ -17,30 +17,32 @@ router.get("/info", async (req: Request, res: Response) => { return res.status(401).json({ success: false, message: "인증 정보 없음" }); } - const conditions: string[] = ["company_code = $1", "is_active = 'Y'"]; + const conditions: string[] = ["iii.company_code = $1", "iii.is_active = '사용'"]; const params: unknown[] = [companyCode]; let idx = 2; if (itemCode) { - conditions.push(`item_code = $${idx++}`); + conditions.push(`iii.item_code = $${idx++}`); params.push(itemCode); } if (itemId) { - conditions.push(`item_id = $${idx++}`); + conditions.push(`iii.item_id = $${idx++}`); params.push(itemId); } if (inspectionType) { - conditions.push(`inspection_type = $${idx++}`); + conditions.push(`iii.inspection_type = $${idx++}`); params.push(inspectionType); } const sql = ` - SELECT id, item_id, item_code, item_name, - inspection_type, inspection_item_name, inspection_standard, - inspection_method, pass_criteria, is_required, sort_order, memo - FROM item_inspection_info + SELECT iii.id, iii.item_id, iii.item_code, iii.item_name, + iii.inspection_type, iii.inspection_item_name, iii.inspection_standard, + iii.inspection_method, iii.pass_criteria, iii.is_required, iii.sort_order, iii.memo, + ist.judgment_criteria + FROM item_inspection_info iii + LEFT JOIN inspection_standard ist ON ist.id = iii.inspection_standard_id WHERE ${conditions.join(" AND ")} - ORDER BY sort_order, inspection_item_name + ORDER BY iii.sort_order, iii.inspection_item_name `; try { diff --git a/frontend/app/(main)/COMPANY_10/pop/_components/inbound/InspectionModal.tsx b/frontend/app/(main)/COMPANY_10/pop/_components/inbound/InspectionModal.tsx index 1bf9079f..e47f8e2c 100644 --- a/frontend/app/(main)/COMPANY_10/pop/_components/inbound/InspectionModal.tsx +++ b/frontend/app/(main)/COMPANY_10/pop/_components/inbound/InspectionModal.tsx @@ -14,6 +14,8 @@ export interface InspectionItem { inspection_method: string; pass_criteria: string; is_required: string; + /** "CAT_JC_01" 수치(범위) | "CAT_JC_02" 텍스트입력 | "CAT_JC_03" O/X | "CAT_JC_04" 선택형 */ + judgment_criteria?: string; /** User-entered measured value */ measured_value: string; /** "pass" | "fail" | null */ @@ -143,6 +145,7 @@ export function InspectionModal({ inspection_method: String(r.inspection_method ?? ""), pass_criteria: String(r.pass_criteria ?? ""), is_required: String(r.is_required ?? "Y"), + judgment_criteria: String(r.judgment_criteria ?? ""), measured_value: "", result: null, })), @@ -397,23 +400,25 @@ export function InspectionModal({ {/* Input + result buttons */}
- + {item.judgment_criteria !== "CAT_JC_03" && ( + + )}
+ {item.judgment_criteria !== "CAT_JC_03" && ( + + )}
+ {item.judgment_criteria !== "CAT_JC_03" && ( + + )}
+ {item.judgment_criteria !== "CAT_JC_03" && ( + + )}
+ {item.judgment_criteria !== "CAT_JC_03" && ( + + )}
+ {item.judgment_criteria !== "CAT_JC_03" && ( + + )}
+ {item.judgment_criteria !== "CAT_JC_03" && ( + + )}