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