fix(WEB): 산출내역서 BOM 자재 표시 개선 (#5, #6)

이슈 #5: 산출내역서 담당자/연락처/단위 표시
이슈 #6: 세부산출내역 vs 소요자재내역 분리

주요 변경:
- QuoteCalculationReport.tsx: bomMaterials 사용하여 소요자재 내역 표시
- 세부산출내역과 소요자재내역 데이터 소스 분리
This commit is contained in:
2026-01-06 21:20:56 +09:00
parent 1c338f4d3f
commit b52e9c70af

View File

@@ -5,9 +5,12 @@
*/ */
import { QuoteFormData } from "./QuoteRegistration"; import { QuoteFormData } from "./QuoteRegistration";
import type { BomMaterial } from "./types";
import type { CompanyFormData } from "@/components/settings/CompanyInfoManagement/types";
interface QuoteCalculationReportProps { interface QuoteCalculationReportProps {
quote: QuoteFormData; quote: QuoteFormData;
companyInfo?: CompanyFormData | null;
documentType?: "견적산출내역서" | "견적서"; documentType?: "견적산출내역서" | "견적서";
showDetailedBreakdown?: boolean; showDetailedBreakdown?: boolean;
showMaterialList?: boolean; showMaterialList?: boolean;
@@ -15,6 +18,7 @@ interface QuoteCalculationReportProps {
export function QuoteCalculationReport({ export function QuoteCalculationReport({
quote, quote,
companyInfo,
documentType = "견적산출내역서", documentType = "견적산출내역서",
showDetailedBreakdown = true, showDetailedBreakdown = true,
showMaterialList = true showMaterialList = true
@@ -30,19 +34,26 @@ export function QuoteCalculationReport({
return `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}`; return `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}`;
}; };
// 총 금액 계산 // 총 금액 계산 (totalAmount > unitPrice * quantity > inspectionFee 우선순위)
const totalAmount = quote.items?.reduce((sum, item) => { const totalAmount = quote.items?.reduce((sum, item) => {
return sum + (item.inspectionFee || 0) * (item.quantity || 1); const itemTotal = item.totalAmount ||
(item.unitPrice || 0) * (item.quantity || 1) ||
(item.inspectionFee || 0) * (item.quantity || 1);
return sum + itemTotal;
}, 0) || 0; }, 0) || 0;
// 소요자재 내역 생성 (샘플 데이터) // 소요자재 내역 - BOM 자재 목록 (quote.bomMaterials)에서 가져옴
const materialItems = quote.items?.map((item, index) => ({ // bomMaterials가 없으면 빈 배열 (BOM 계산 데이터 없음)
const materialItems = (quote.bomMaterials || []).map((material, index) => ({
no: index + 1, no: index + 1,
name: item.productName || '가이드레일', itemCode: material.itemCode || '-',
spec: `${item.openWidth || 0}×${item.openHeight || 0}mm`, name: material.itemName || '-',
quantity: item.quantity || 1, spec: material.specification || '-',
unit: 'SET' quantity: Math.floor(material.quantity || 1),
})) || []; unit: material.unit || 'EA',
unitPrice: material.unitPrice || 0,
totalPrice: material.totalPrice || 0,
}));
return ( return (
<> <>
@@ -316,29 +327,29 @@ export function QuoteCalculationReport({
<tbody> <tbody>
<tr> <tr>
<th></th> <th></th>
<td>()</td> <td>{companyInfo?.companyName || '-'}</td>
<th></th> <th></th>
<td>139-87-00353</td> <td>{companyInfo?.businessNumber || '-'}</td>
</tr> </tr>
<tr> <tr>
<th></th> <th></th>
<td> </td> <td>{companyInfo?.representativeName || '-'}</td>
<th></th> <th></th>
<td></td> <td>{companyInfo?.businessType || '-'}</td>
</tr> </tr>
<tr> <tr>
<th></th> <th></th>
<td colSpan={3}>, , </td> <td colSpan={3}>{companyInfo?.businessCategory || '-'}</td>
</tr> </tr>
<tr> <tr>
<th></th> <th></th>
<td colSpan={3}> 45-22</td> <td colSpan={3}>{companyInfo?.address || '-'}</td>
</tr> </tr>
<tr> <tr>
<th></th> <th></th>
<td>031-983-5130</td> <td>{companyInfo?.managerPhone || '-'}</td>
<th></th> <th></th>
<td>02-6911-6315</td> <td>{companyInfo?.email || '-'}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -370,17 +381,23 @@ export function QuoteCalculationReport({
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{quote.items.map((item, index) => ( {quote.items.map((item, index) => {
<tr key={item.id || `item-${index}`}> // 단가: unitPrice > inspectionFee 우선순위
<td style={{ textAlign: 'center' }}>{index + 1}</td> const unitPrice = item.unitPrice || item.inspectionFee || 0;
<td>{item.productName}</td> // 금액: totalAmount > unitPrice * quantity 우선순위
<td style={{ fontSize: '11px' }}>{`${item.openWidth}×${item.openHeight}mm`}</td> const itemTotal = item.totalAmount || unitPrice * (item.quantity || 1);
<td style={{ textAlign: 'right' }}>{item.quantity}</td> return (
<td style={{ textAlign: 'center' }}>SET</td> <tr key={item.id || `item-${index}`}>
<td style={{ textAlign: 'right' }}>{formatAmount(item.inspectionFee)}</td> <td style={{ textAlign: 'center' }}>{index + 1}</td>
<td style={{ textAlign: 'right', fontWeight: '600' }}>{formatAmount((item.inspectionFee || 0) * (item.quantity || 1))}</td> <td>{item.productName}</td>
</tr> <td style={{ fontSize: '11px' }}>{`${item.openWidth}×${item.openHeight}mm`}</td>
))} <td style={{ textAlign: 'right' }}>{Math.floor(item.quantity || 0)}</td>
<td style={{ textAlign: 'center' }}>{item.unit || 'SET'}</td>
<td style={{ textAlign: 'right' }}>{formatAmount(unitPrice)}</td>
<td style={{ textAlign: 'right', fontWeight: '600' }}>{formatAmount(itemTotal)}</td>
</tr>
);
})}
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
@@ -416,7 +433,7 @@ export function QuoteCalculationReport({
</tr> </tr>
<tr> <tr>
<th></th> <th></th>
<td>{quote.items?.[0]?.quantity || 1} SET</td> <td>{Math.floor(quote.items?.[0]?.quantity || 1)} {quote.items?.[0]?.unit || 'SET'}</td>
<th></th> <th></th>
<td>2438 × 550 (mm)</td> <td>2438 × 550 (mm)</td>
</tr> </tr>
@@ -426,28 +443,42 @@ export function QuoteCalculationReport({
</div> </div>
{/* 자재 목록 테이블 */} {/* 자재 목록 테이블 */}
<table className="material-table"> {materialItems.length > 0 ? (
<thead> <table className="material-table">
<tr> <thead>
<th style={{ width: '40px' }}>No.</th> <tr>
<th></th> <th style={{ width: '40px' }}>No.</th>
<th style={{ width: '250px' }}></th> <th style={{ width: '100px' }}></th>
<th style={{ width: '80px' }}></th> <th></th>
<th style={{ width: '60px' }}></th> <th style={{ width: '200px' }}></th>
</tr> <th style={{ width: '80px' }}></th>
</thead> <th style={{ width: '60px' }}></th>
<tbody>
{materialItems.map((item, index) => (
<tr key={index}>
<td style={{ textAlign: 'center' }}>{index + 1}</td>
<td>{item.name}</td>
<td>{item.spec}</td>
<td style={{ textAlign: 'center', fontWeight: '600' }}>{item.quantity}</td>
<td style={{ textAlign: 'center' }}>{item.unit}</td>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {materialItems.map((item, index) => (
<tr key={index}>
<td style={{ textAlign: 'center' }}>{index + 1}</td>
<td style={{ textAlign: 'center', fontSize: '11px' }}>{item.itemCode}</td>
<td>{item.name}</td>
<td style={{ fontSize: '11px' }}>{item.spec}</td>
<td style={{ textAlign: 'center', fontWeight: '600' }}>{item.quantity}</td>
<td style={{ textAlign: 'center' }}>{item.unit}</td>
</tr>
))}
</tbody>
</table>
) : (
<div style={{
border: '2px solid #000',
padding: '30px',
textAlign: 'center',
marginTop: '15px',
color: '#666'
}}>
. (BOM )
</div>
)}
</div> </div>
)} )}
@@ -479,7 +510,7 @@ export function QuoteCalculationReport({
<div> <div>
<div style={{ fontSize: '13px', marginBottom: '5px' }}>{formatDate(quote.registrationDate || '')}</div> <div style={{ fontSize: '13px', marginBottom: '5px' }}>{formatDate(quote.registrationDate || '')}</div>
<div style={{ fontSize: '15px', fontWeight: '600' }}> <div style={{ fontSize: '15px', fontWeight: '600' }}>
: () () : {companyInfo?.companyName || '-'} ()
</div> </div>
</div> </div>
<div className="stamp-area"> <div className="stamp-area">
@@ -499,7 +530,7 @@ export function QuoteCalculationReport({
<p>3. .</p> <p>3. .</p>
<p>4. .</p> <p>4. .</p>
<p style={{ marginTop: '12px', textAlign: 'center', fontWeight: '600' }}> <p style={{ marginTop: '12px', textAlign: 'center', fontWeight: '600' }}>
: {quote.manager || '담당자'} | {quote.contact || '031-983-5130'} : {companyInfo?.managerName || quote.manager || '담당자'} | {companyInfo?.managerPhone || '-'}
</p> </p>
</div> </div>
</div> </div>