fix: 산출내역서 세부항목 표시 및 부호 필드 수정
- 세부산출내역서: 개소별 BOM 품목 상세 표시 (품명/규격/수량/단가/금액) - 개소별 소계 행 추가 및 전체 합계 표시 - 부호 표시: loc.symbol → loc.code로 수정 (내역/세부산출 양쪽) - types.ts: 할인율/할인금액 필드 추가 (discountRate, discountAmount)
This commit is contained in:
@@ -204,7 +204,7 @@ export function QuotePreviewContent({
|
||||
<tr key={loc.id} className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{index + 1}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.floor || '-'}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.symbol || '-'}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.code || '-'}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">{loc.productCode}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.openWidth}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.openHeight}</td>
|
||||
@@ -301,45 +301,94 @@ export function QuotePreviewContent({
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b border-gray-400">
|
||||
<th className="border-r border-gray-300 px-2 py-1">부호</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1">항목</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1 w-16">부호</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1">품명</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1">규격</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1">수량</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1">단위</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1">단가</th>
|
||||
<th className="px-2 py-1">합계</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1 w-16">수량</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1 w-12">단위</th>
|
||||
<th className="border-r border-gray-300 px-2 py-1 w-20">단가</th>
|
||||
<th className="px-2 py-1 w-24">금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{quoteData.locations.map((loc) => (
|
||||
<React.Fragment key={loc.id}>
|
||||
{/* 각 개소별 품목 상세 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.symbol || '-'}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">항목명</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">규격명</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.quantity}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">SET</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-right">
|
||||
{(loc.unitPrice || 0).toLocaleString()}
|
||||
</td>
|
||||
<td className="px-2 py-1 text-right">
|
||||
{(loc.totalPrice || 0).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{/* 소계 */}
|
||||
<tr className="bg-gray-50 border-t border-gray-400">
|
||||
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-center font-semibold">
|
||||
소계
|
||||
{quoteData.locations.map((loc, locIndex) => {
|
||||
const bomItems = loc.bomResult?.items || [];
|
||||
const locationSymbol = loc.code || `${loc.floor}-${locIndex + 1}`;
|
||||
const locationSubtotal = loc.bomResult?.grand_total || loc.totalPrice || 0;
|
||||
|
||||
return (
|
||||
<React.Fragment key={loc.id}>
|
||||
{/* 개소 헤더 */}
|
||||
<tr className="bg-blue-50 border-b border-gray-400">
|
||||
<td colSpan={7} className="px-2 py-1 font-semibold text-blue-800">
|
||||
[{locationSymbol}] {loc.floor} - {loc.name || loc.code} (수량: {loc.quantity}개)
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* BOM 품목 상세 */}
|
||||
{bomItems.length > 0 ? (
|
||||
bomItems.map((item, itemIndex) => (
|
||||
<tr key={`${loc.id}-${itemIndex}`} className="border-b border-gray-200">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center text-gray-500">
|
||||
{itemIndex === 0 ? locationSymbol : ''}
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">
|
||||
{item.item_name || item.item_code}
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">
|
||||
{item.specification || '-'}
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">
|
||||
{item.quantity}
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">
|
||||
{item.unit || 'EA'}
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-right">
|
||||
{item.unit_price.toLocaleString()}
|
||||
</td>
|
||||
<td className="px-2 py-1 text-right">
|
||||
{item.total_price.toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr className="border-b border-gray-200">
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{locationSymbol}</td>
|
||||
<td colSpan={6} className="px-2 py-1 text-center text-gray-400">
|
||||
BOM 계산 결과 없음
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{/* 개소별 소계 */}
|
||||
<tr className="bg-gray-100 border-b border-gray-400">
|
||||
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-right font-semibold">
|
||||
[{locationSymbol}] 소계 ({loc.quantity}개)
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center font-semibold">
|
||||
{loc.quantity}
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1"></td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-right">
|
||||
{(loc.bomResult?.grand_total || 0).toLocaleString()}
|
||||
</td>
|
||||
<td className="px-2 py-1 text-right font-semibold">
|
||||
{(locationSubtotal * loc.quantity).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 전체 합계 */}
|
||||
<tr className="bg-gray-200 border-t-2 border-gray-500">
|
||||
<td colSpan={6} className="border-r border-gray-300 px-2 py-2 text-center font-bold">
|
||||
총 합계
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">
|
||||
{quoteData.locations.reduce((sum, loc) => sum + (loc.quantity || 0), 0)}
|
||||
<td className="px-2 py-2 text-right font-bold text-lg">
|
||||
{subtotal.toLocaleString()}
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1"></td>
|
||||
<td className="border-r border-gray-300 px-2 py-1"></td>
|
||||
<td className="px-2 py-1 text-right font-semibold">{subtotal.toLocaleString()}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -158,6 +158,8 @@ export interface QuoteApiData {
|
||||
supply_amount: string | number;
|
||||
tax_amount: string | number;
|
||||
total_amount: string | number;
|
||||
discount_rate?: number | null; // 할인율 (%)
|
||||
discount_amount?: number | null; // 할인 금액
|
||||
status: QuoteStatus;
|
||||
current_revision: number;
|
||||
is_final: boolean;
|
||||
@@ -688,6 +690,8 @@ export interface QuoteFormDataV2 {
|
||||
dueDate: string;
|
||||
remarks: string;
|
||||
status: 'draft' | 'temporary' | 'final'; // 작성중, 임시저장, 최종저장
|
||||
discountRate: number; // 할인율 (%)
|
||||
discountAmount: number; // 할인 금액
|
||||
locations: LocationItem[];
|
||||
}
|
||||
|
||||
@@ -853,6 +857,8 @@ export function transformV2ToApi(
|
||||
quantity: data.locations.reduce((sum, loc) => sum + loc.quantity, 0),
|
||||
unit_symbol: '개소',
|
||||
total_amount: grandTotal,
|
||||
discount_rate: data.discountRate || 0,
|
||||
discount_amount: data.discountAmount || 0,
|
||||
status: data.status === 'final' ? 'finalized' : 'draft',
|
||||
is_final: data.status === 'final',
|
||||
calculation_inputs: calculationInputs,
|
||||
@@ -972,6 +978,8 @@ export function transformApiToV2(apiData: QuoteApiData): QuoteFormDataV2 {
|
||||
// raw API: remarks || description, transformed: description
|
||||
remarks: apiData.remarks || apiData.description || transformed.description || '',
|
||||
status: mapStatus(apiData.status),
|
||||
discountRate: Number(apiData.discount_rate) || 0,
|
||||
discountAmount: Number(apiData.discount_amount) || 0,
|
||||
locations: locations,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user