From 4c4f0678d273012879ab3b79ffcef20ac9d666ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Fri, 20 Feb 2026 23:52:12 +0900 Subject: [PATCH] =?UTF-8?q?fix(WEB):=20=EC=A0=88=EA=B3=A1=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=EC=9D=BC=EC=A7=80=20=EB=A0=88=EA=B1=B0=EC=8B=9C=20?= =?UTF-8?q?=EC=9D=BC=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 셔터박스 테이블 구성요소 기준 정렬 (파트→길이 순서) - 마구리 무게: 원본 box 크기로 계산 (레거시 일치) - LOT 접두사 제거: 4개 섹션 모두 "-" 표시 - 가이드레일 하부BASE 치수 표시 수정 --- .../documents/bending/BottomBarSection.tsx | 4 +- .../documents/bending/GuideRailSection.tsx | 12 +- .../documents/bending/ShutterBoxSection.tsx | 4 +- .../documents/bending/SmokeBarrierSection.tsx | 2 +- .../WorkOrders/documents/bending/types.ts | 3 +- .../WorkOrders/documents/bending/utils.ts | 170 +++++++++--------- 6 files changed, 94 insertions(+), 101 deletions(-) diff --git a/src/components/production/WorkOrders/documents/bending/BottomBarSection.tsx b/src/components/production/WorkOrders/documents/bending/BottomBarSection.tsx index 0324a415..2ce79584 100644 --- a/src/components/production/WorkOrders/documents/bending/BottomBarSection.tsx +++ b/src/components/production/WorkOrders/documents/bending/BottomBarSection.tsx @@ -55,9 +55,7 @@ export function BottomBarSection({ bendingInfo, mapping }: BottomBarSectionProps {row.material} {fmt(row.length)} {fmt(row.quantity)} - - {row.lotPrefix}- - + - {fmtWeight(row.weight)} ))} diff --git a/src/components/production/WorkOrders/documents/bending/GuideRailSection.tsx b/src/components/production/WorkOrders/documents/bending/GuideRailSection.tsx index bad051fe..4fa924ad 100644 --- a/src/components/production/WorkOrders/documents/bending/GuideRailSection.tsx +++ b/src/components/production/WorkOrders/documents/bending/GuideRailSection.tsx @@ -60,9 +60,7 @@ function PartTable({ title, rows, imageUrl, lotNo, baseSize }: { {row.partName === '하부BASE' ? (baseSize || '-') : fmt(row.length)} {fmt(row.quantity)} - - {row.lotPrefix}- - + - {fmtWeight(row.weight)} ))} @@ -79,11 +77,11 @@ export function GuideRailSection({ bendingInfo, mapping, lotNo }: GuideRailSecti const productCode = bendingInfo.productCode; const wallRows = wall - ? buildWallGuideRailRows(wall.lengthData, wall.baseSize, mapping) + ? buildWallGuideRailRows(wall.lengthData, wall.baseDimension || '135*80', mapping) : []; const sideRows = side - ? buildSideGuideRailRows(side.lengthData, mapping) + ? buildSideGuideRailRows(side.lengthData, side.baseDimension || '135*130', mapping) : []; if (wallRows.length === 0 && sideRows.length === 0) return null; @@ -100,7 +98,7 @@ export function GuideRailSection({ bendingInfo, mapping, lotNo }: GuideRailSecti rows={wallRows} imageUrl={getBendingImageUrl('guiderail', productCode, 'wall')} lotNo={lotNo} - baseSize={wall?.baseSize} + baseSize={wall?.baseDimension || wall?.baseSize} /> )} @@ -110,7 +108,7 @@ export function GuideRailSection({ bendingInfo, mapping, lotNo }: GuideRailSecti rows={sideRows} imageUrl={getBendingImageUrl('guiderail', productCode, 'side')} lotNo={lotNo} - baseSize="135*130" + baseSize={side?.baseDimension || '135*130'} /> )} diff --git a/src/components/production/WorkOrders/documents/bending/ShutterBoxSection.tsx b/src/components/production/WorkOrders/documents/bending/ShutterBoxSection.tsx index ee302b98..2db3eff4 100644 --- a/src/components/production/WorkOrders/documents/bending/ShutterBoxSection.tsx +++ b/src/components/production/WorkOrders/documents/bending/ShutterBoxSection.tsx @@ -70,9 +70,7 @@ function ShutterBoxSubSection({ box, index }: { box: ShutterBoxData; index: numb {row.material} {row.dimension} {fmt(row.quantity)} - - {row.lotPrefix}- - + - {fmtWeight(row.weight)} ))} diff --git a/src/components/production/WorkOrders/documents/bending/SmokeBarrierSection.tsx b/src/components/production/WorkOrders/documents/bending/SmokeBarrierSection.tsx index 10b02b39..49518a54 100644 --- a/src/components/production/WorkOrders/documents/bending/SmokeBarrierSection.tsx +++ b/src/components/production/WorkOrders/documents/bending/SmokeBarrierSection.tsx @@ -55,7 +55,7 @@ export function SmokeBarrierSection({ bendingInfo }: SmokeBarrierSectionProps) { {row.material} {fmt(row.length)} {fmt(row.quantity)} - {row.lotCode} + - {fmtWeight(row.weight)} ))} diff --git a/src/components/production/WorkOrders/documents/bending/types.ts b/src/components/production/WorkOrders/documents/bending/types.ts index 91b171ca..f0dd4ed8 100644 --- a/src/components/production/WorkOrders/documents/bending/types.ts +++ b/src/components/production/WorkOrders/documents/bending/types.ts @@ -14,7 +14,8 @@ export interface LengthQuantity { // 가이드레일 타입별 데이터 export interface GuideRailTypeData { lengthData: LengthQuantity[]; - baseSize: string; // "135*80" 또는 "135*130" + baseSize: string; // BOM 프로파일 사이즈 "130*75" (섹션 제목용) + baseDimension?: string; // 실제 하부BASE 물리 치수 "135*130" (작업일지 표시/무게계산용) } // 셔터박스 데이터 diff --git a/src/components/production/WorkOrders/documents/bending/utils.ts b/src/components/production/WorkOrders/documents/bending/utils.ts index ee79d3ca..85152e8d 100644 --- a/src/components/production/WorkOrders/documents/bending/utils.ts +++ b/src/components/production/WorkOrders/documents/bending/utils.ts @@ -27,9 +27,8 @@ const EGI_DENSITY = 7.85; // g/cm3 // 가이드레일 const WALL_PART_WIDTH = 412; // mm - 벽면형 파트 폭 const SIDE_PART_WIDTH = 462; // mm - 측면형 파트 폭 -const WALL_BASE_HEIGHT_MIXED = 80; // 혼합형 벽면 하부BASE 높이 -const WALL_BASE_HEIGHT_ONLY = 130; // 벽면형 단독 하부BASE 높이 -const SIDE_BASE_HEIGHT = 130; // 측면형 하부BASE 높이 +const WALL_BASE_HEIGHT = 80; // 벽면형 하부BASE 높이 (legacy: wall_basesize 135*80) +const SIDE_BASE_HEIGHT = 130; // 측면형 하부BASE 높이 (legacy: side_basesize 135*130) const BASE_WIDTH = 135; // 하부BASE 폭 // 하단마감재 @@ -48,6 +47,21 @@ const GUIDE_RAIL_LENGTH_BUCKETS = [2438, 3000, 3500, 4000, 4300]; const SHUTTER_BOX_LENGTH_BUCKETS = [1219, 2438, 3000, 3500, 4000, 4150]; const SMOKE_W50_LENGTH_BUCKETS = [2438, 3000, 3500, 4000, 4300]; +// ============================================================ +// 하부BASE 치수 파싱 헬퍼 +// ============================================================ + +/** baseDimension 문자열("135*130")에서 width/height 파싱. 없으면 기본값 사용 */ +function parseBaseDimension(baseDimension: string | undefined, fallbackHeight: number): { width: number; height: number } { + if (baseDimension) { + const parts = baseDimension.split('*').map(Number); + if (parts.length === 2 && parts[0] > 0 && parts[1] > 0) { + return { width: parts[0], height: parts[1] }; + } + } + return { width: BASE_WIDTH, height: fallbackHeight }; +} + // ============================================================ // 핵심 함수: calWeight (PHP Lines 27-55) // ============================================================ @@ -165,12 +179,10 @@ export function getSLengthCode(length: number, category: string): string | null */ export function buildWallGuideRailRows( lengthData: LengthQuantity[], - baseSize: string, + baseDimension: string, mapping: MaterialMapping, ): GuideRailPartRow[] { const rows: GuideRailPartRow[] = []; - const baseHeight = baseSize === '135*80' ? WALL_BASE_HEIGHT_MIXED : WALL_BASE_HEIGHT_ONLY; - for (const ld of lengthData) { if (ld.quantity <= 0) continue; @@ -211,9 +223,11 @@ export function buildWallGuideRailRows( } // 하부BASE (길이 데이터와 무관하게 1행) + // baseDimension: "135*130" (KQTS01/KTE01) 또는 "135*80" (기타) const totalQty = lengthData.reduce((sum, ld) => sum + ld.quantity, 0); if (totalQty > 0) { - const baseW = calcWeight('EGI 1.55T', BASE_WIDTH, baseHeight); + const { width: bw, height: bh } = parseBaseDimension(baseDimension, WALL_BASE_HEIGHT); + const baseW = calcWeight('EGI 1.55T', bw, bh); rows.push({ partName: '하부BASE', lotPrefix: 'XX', material: 'EGI 1.55T', length: 0, quantity: totalQty, weight: Math.round(baseW.weight * totalQty * 100) / 100, @@ -228,6 +242,7 @@ export function buildWallGuideRailRows( */ export function buildSideGuideRailRows( lengthData: LengthQuantity[], + baseDimension: string, mapping: MaterialMapping, ): GuideRailPartRow[] { const rows: GuideRailPartRow[] = []; @@ -259,9 +274,11 @@ export function buildSideGuideRailRows( } // 하부BASE + // baseDimension: "135*130" (측면형은 항상 135*130) const totalQty = lengthData.reduce((sum, ld) => sum + ld.quantity, 0); if (totalQty > 0) { - const baseW = calcWeight('EGI 1.55T', BASE_WIDTH, SIDE_BASE_HEIGHT); + const { width: bw, height: bh } = parseBaseDimension(baseDimension, SIDE_BASE_HEIGHT); + const baseW = calcWeight('EGI 1.55T', bw, bh); rows.push({ partName: '하부BASE', lotPrefix: 'XX', material: 'EGI 1.55T', length: 0, quantity: totalQty, weight: Math.round(baseW.weight * totalQty * 100) / 100, @@ -341,93 +358,74 @@ export function buildShutterBoxRows(box: ShutterBoxData): ShutterBoxPartRow[] { const { width: boxWidth, height: boxHeight } = parseBoxSize(box.size); const isStandard = box.size === '500*380'; - for (const ld of box.lengthData) { - if (ld.quantity <= 0) continue; + // 방향별 파트 정의 + let parts: { name: string; prefix: string; dim: number }[]; - if (isStandard) { - // 표준 500*380 구성 - const parts = [ - { name: '①전면부', prefix: 'CF', dim: boxHeight + 122 }, - { name: '②린텔부', prefix: 'CL', dim: boxWidth - 330 }, - { name: '③⑤점검구', prefix: 'CP', dim: boxWidth - 200 }, - { name: '④후면코너부', prefix: 'CB', dim: 170 }, - ]; - for (const p of parts) { - const w = calcWeight(BOX_FINISH_MATERIAL, p.dim, ld.length); - rows.push({ - partName: p.name, lotPrefix: p.prefix, material: BOX_FINISH_MATERIAL, - dimension: `${ld.length}`, quantity: ld.quantity, - weight: Math.round(w.weight * ld.quantity * 100) / 100, - }); - } - } else if (box.direction === '양면') { - const parts = [ - { name: '①전면부', prefix: 'XX', dim: boxHeight + 122 }, - { name: '②린텔부', prefix: 'CL', dim: boxWidth - 330 }, - { name: '③점검구', prefix: 'XX', dim: boxWidth - 200 }, - { name: '④후면코너부', prefix: 'CB', dim: 170 }, - { name: '⑤점검구', prefix: 'XX', dim: boxHeight - 100 }, - ]; - for (const p of parts) { - const w = calcWeight(BOX_FINISH_MATERIAL, p.dim, ld.length); - rows.push({ - partName: p.name, lotPrefix: p.prefix, material: BOX_FINISH_MATERIAL, - dimension: `${ld.length}`, quantity: ld.quantity, - weight: Math.round(w.weight * ld.quantity * 100) / 100, - }); - } - } else if (box.direction === '밑면') { - const parts = [ - { name: '①전면부', prefix: 'XX', dim: boxHeight + 122 }, - { name: '②린텔부', prefix: 'CL', dim: boxWidth - 330 }, - { name: '③점검구', prefix: 'XX', dim: boxWidth - 200 }, - { name: '④후면부', prefix: 'CB', dim: boxHeight + 85 * 2 }, - ]; - for (const p of parts) { - const w = calcWeight(BOX_FINISH_MATERIAL, p.dim, ld.length); - rows.push({ - partName: p.name, lotPrefix: p.prefix, material: BOX_FINISH_MATERIAL, - dimension: `${ld.length}`, quantity: ld.quantity, - weight: Math.round(w.weight * ld.quantity * 100) / 100, - }); - } - } else if (box.direction === '후면') { - const parts = [ - { name: '①전면부', prefix: 'XX', dim: boxHeight + 122 }, - { name: '②린텔부', prefix: 'CL', dim: boxWidth + 85 * 2 }, - { name: '③점검구', prefix: 'XX', dim: boxHeight - 200 }, - { name: '④후면코너부', prefix: 'CB', dim: boxHeight + 85 * 2 }, - ]; - for (const p of parts) { - const w = calcWeight(BOX_FINISH_MATERIAL, p.dim, ld.length); - rows.push({ - partName: p.name, lotPrefix: p.prefix, material: BOX_FINISH_MATERIAL, - dimension: `${ld.length}`, quantity: ld.quantity, - weight: Math.round(w.weight * ld.quantity * 100) / 100, - }); - } + if (isStandard) { + parts = [ + { name: '①전면부', prefix: 'CF', dim: boxHeight + 122 }, + { name: '②린텔부', prefix: 'CL', dim: boxWidth - 330 }, + { name: '③⑤점검구', prefix: 'CP', dim: boxWidth - 200 }, + { name: '④후면코너부', prefix: 'CB', dim: 170 }, + ]; + } else if (box.direction === '양면') { + parts = [ + { name: '①전면부', prefix: 'XX', dim: boxHeight + 122 }, + { name: '②린텔부', prefix: 'CL', dim: boxWidth - 330 }, + { name: '③점검구', prefix: 'XX', dim: boxWidth - 200 }, + { name: '④후면코너부', prefix: 'CB', dim: 170 }, + { name: '⑤점검구', prefix: 'XX', dim: boxHeight - 100 }, + ]; + } else if (box.direction === '밑면') { + parts = [ + { name: '①전면부', prefix: 'XX', dim: boxHeight + 122 }, + { name: '②린텔부', prefix: 'CL', dim: boxWidth - 330 }, + { name: '③점검구', prefix: 'XX', dim: boxWidth - 200 }, + { name: '④후면부', prefix: 'CB', dim: boxHeight + 85 * 2 }, + ]; + } else if (box.direction === '후면') { + parts = [ + { name: '①전면부', prefix: 'XX', dim: boxHeight + 122 }, + { name: '②린텔부', prefix: 'CL', dim: boxWidth + 85 * 2 }, + { name: '③점검구', prefix: 'XX', dim: boxHeight - 200 }, + { name: '④후면코너부', prefix: 'CB', dim: boxHeight + 85 * 2 }, + ]; + } else { + parts = []; + } + + // 구성요소 기준 정렬: 파트 → 길이 순서 + for (const p of parts) { + for (const ld of box.lengthData) { + if (ld.quantity <= 0) continue; + const w = calcWeight(BOX_FINISH_MATERIAL, p.dim, ld.length); + rows.push({ + partName: p.name, lotPrefix: p.prefix, material: BOX_FINISH_MATERIAL, + dimension: `${ld.length}`, quantity: ld.quantity, + weight: Math.round(w.weight * ld.quantity * 100) / 100, + }); } } - // 상부덮개 (비표준일 때) - if (!isStandard && box.coverQty > 0) { + // 상부덮개 + if (box.coverQty > 0) { const coverWidth = boxWidth - 111; const w = calcWeight(BOX_FINISH_MATERIAL, coverWidth, BOX_COVER_LENGTH); rows.push({ - partName: '⑥상부덮개', lotPrefix: 'XX', material: BOX_FINISH_MATERIAL, + partName: isStandard ? '⑤상부덮개' : (box.direction === '양면' ? '⑥상부덮개' : '⑤상부덮개'), + lotPrefix: 'XX', material: BOX_FINISH_MATERIAL, dimension: `1219 * ${coverWidth}`, quantity: box.coverQty, weight: Math.round(w.weight * box.coverQty * 100) / 100, }); } - // 마구리 (비표준일 때) - if (!isStandard && box.finCoverQty > 0) { - const finWidth = boxWidth + 5; - const finHeight = boxHeight + 5; - const w = calcWeight(BOX_FINISH_MATERIAL, finWidth, finHeight); + // 마구리 (레거시: 무게는 원본 box 크기로 계산, 표시 치수만 +5) + if (box.finCoverQty > 0) { + const w = calcWeight(BOX_FINISH_MATERIAL, boxWidth, boxHeight); rows.push({ - partName: '⑦측면부(마구리)', lotPrefix: 'XX', material: BOX_FINISH_MATERIAL, - dimension: `${finWidth} * ${finHeight}`, quantity: box.finCoverQty, + partName: isStandard ? '⑥측면부(마구리)' : (box.direction === '양면' ? '⑦측면부(마구리)' : '⑥측면부(마구리)'), + lotPrefix: 'XX', material: BOX_FINISH_MATERIAL, + dimension: `${boxWidth + 5} * ${boxHeight + 5}`, quantity: box.finCoverQty, weight: Math.round(w.weight * box.finCoverQty * 100) / 100, }); } @@ -493,8 +491,6 @@ export function calculateProductionSummary( // 가이드레일 - 벽면형 if (bendingInfo.guideRail.wall) { - const baseHeight = bendingInfo.guideRail.wall.baseSize === '135*80' - ? WALL_BASE_HEIGHT_MIXED : WALL_BASE_HEIGHT_ONLY; for (const ld of bendingInfo.guideRail.wall.lengthData) { if (ld.quantity <= 0) continue; addWeight(mapping.guideRailFinish, WALL_PART_WIDTH, ld.length, ld.quantity); @@ -504,7 +500,8 @@ export function calculateProductionSummary( } } const totalWallQty = bendingInfo.guideRail.wall.lengthData.reduce((s, l) => s + l.quantity, 0); - addWeight('EGI 1.55T', BASE_WIDTH, baseHeight, totalWallQty); + const wallBase = parseBaseDimension(bendingInfo.guideRail.wall.baseDimension, WALL_BASE_HEIGHT); + addWeight('EGI 1.55T', wallBase.width, wallBase.height, totalWallQty); } // 가이드레일 - 측면형 @@ -515,7 +512,8 @@ export function calculateProductionSummary( addWeight(mapping.bodyMaterial, SIDE_PART_WIDTH, ld.length, ld.quantity * 3); } const totalSideQty = bendingInfo.guideRail.side.lengthData.reduce((s, l) => s + l.quantity, 0); - addWeight('EGI 1.55T', BASE_WIDTH, SIDE_BASE_HEIGHT, totalSideQty); + const sideBase = parseBaseDimension(bendingInfo.guideRail.side.baseDimension, SIDE_BASE_HEIGHT); + addWeight('EGI 1.55T', sideBase.width, sideBase.height, totalSideQty); } // 하단마감재