feat(WEB): 자재/영업/품질 모듈 기능 개선 및 문서 컴포넌트 추가
- 입고관리: 상세/목록 UI 개선, actions 로직 강화 - 재고현황: 상세/목록 개선, StockAuditModal 신규 추가 - 영업주문관리: 페이지 구조 개선, OrderSalesDetailEdit 기능 강화 - 주문: OrderRegistration 개선, SalesOrderDocument 신규 추가 - 견적: QuoteTransactionModal 기능 개선 - 품질: InspectionModalV2, ImportInspectionDocument 대폭 개선 - UniversalListPage: 템플릿 기능 확장 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,9 +4,10 @@
|
||||
* 거래명세서 보기 모달
|
||||
*
|
||||
* 견적 데이터를 거래명세서 양식으로 표시
|
||||
* - 공급자/공급받는자 정보
|
||||
* - 결재라인
|
||||
* - 수요자/공급자 정보
|
||||
* - 품목내역
|
||||
* - 금액 계산 (공급가액, 할인, 부가세, 합계)
|
||||
* - 금액 계산 (소계, 할인율, 할인금액, 할인 후 금액, 부가세, 합계)
|
||||
* - 증명 문구 + 인감
|
||||
*/
|
||||
|
||||
@@ -35,18 +36,24 @@ export function QuoteTransactionModal({
|
||||
// locations 배열 (undefined 방어)
|
||||
const locations = quoteData.locations || [];
|
||||
|
||||
// 금액 계산
|
||||
const subtotal = locations.reduce((sum, loc) => {
|
||||
const locationTotal = (loc.items || []).reduce((itemSum, item) => itemSum + (item.amount || 0), 0);
|
||||
return sum + locationTotal * (loc.quantity || 1);
|
||||
}, 0);
|
||||
// 소계 (할인 전 금액)
|
||||
const subtotal = locations.reduce((sum, loc) => sum + (loc.totalPrice || 0), 0);
|
||||
|
||||
// 할인 적용 후 금액
|
||||
const afterDiscount = subtotal - discountAmount;
|
||||
|
||||
// 부가세
|
||||
const vat = Math.round(afterDiscount * 0.1);
|
||||
const finalTotal = afterDiscount + vat;
|
||||
|
||||
// 부가세 포함 여부
|
||||
const vatIncluded = quoteData.vatType === 'included';
|
||||
|
||||
// 총 금액 (부가세 포함 여부에 따라)
|
||||
const grandTotal = vatIncluded ? afterDiscount + vat : afterDiscount;
|
||||
|
||||
// 할인 적용 여부
|
||||
const hasDiscount = discountAmount > 0;
|
||||
|
||||
// 오늘 날짜
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
@@ -57,165 +64,261 @@ export function QuoteTransactionModal({
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<div className="bg-white p-8 min-h-full">
|
||||
{/* 문서 헤더 */}
|
||||
<div className="text-center mb-6">
|
||||
<h1 className="text-2xl font-bold tracking-widest mb-2">거 래 명 세 서</h1>
|
||||
<p className="text-sm text-gray-600">
|
||||
견적번호: {quoteData.quoteNumber || '-'} | 발행일: {quoteData.receiptDate || today}
|
||||
</p>
|
||||
<div className="max-w-[210mm] mx-auto bg-white p-6 text-sm">
|
||||
{/* 헤더: 제목 + 결재란 */}
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
{/* 왼쪽: 제목 */}
|
||||
<div className="flex-1">
|
||||
<h1 className="text-2xl font-bold text-center tracking-[0.5em] mb-1">
|
||||
거 래 명 세 서
|
||||
</h1>
|
||||
<p className="text-xs text-gray-500 text-center">
|
||||
문서번호: {quoteData.quoteNumber || 'ABC123'} | 작성일자: {quoteData.registrationDate || today}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 오른쪽: 결재란 */}
|
||||
<div className="border border-gray-400">
|
||||
<table className="text-xs">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="border-r border-gray-400 px-3 py-1">작성</th>
|
||||
<th className="border-r border-gray-400 px-3 py-1">승인</th>
|
||||
<th className="border-r border-gray-400 px-3 py-1">승인</th>
|
||||
<th className="px-3 py-1">승인</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border-t border-r border-gray-400 px-3 py-3 text-center">홍길동</td>
|
||||
<td className="border-t border-r border-gray-400 px-3 py-3 text-center">이름</td>
|
||||
<td className="border-t border-r border-gray-400 px-3 py-3 text-center">이름</td>
|
||||
<td className="border-t border-gray-400 px-3 py-3 text-center">이름</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-t border-r border-gray-400 px-2 py-1 text-center text-[10px] text-gray-500">부서명</td>
|
||||
<td className="border-t border-r border-gray-400 px-2 py-1 text-center text-[10px] text-gray-500">부서명</td>
|
||||
<td className="border-t border-r border-gray-400 px-2 py-1 text-center text-[10px] text-gray-500">부서명</td>
|
||||
<td className="border-t border-gray-400 px-2 py-1 text-center text-[10px] text-gray-500">부서명</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 공급자/공급받는자 정보 */}
|
||||
{/* 수요자 / 공급자 정보 (좌우 배치) */}
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
{/* 공급자 */}
|
||||
<div className="border border-gray-300">
|
||||
<div className="bg-gray-800 text-white p-2 text-sm font-medium text-center">
|
||||
공급자
|
||||
</div>
|
||||
<div className="p-3 space-y-1 text-sm">
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-600">상호</span>
|
||||
<span>회사명</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-600">대표자</span>
|
||||
<span>홍길동</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-600">사업자번호</span>
|
||||
<span>123-12-12345</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-600">주소</span>
|
||||
<span>주소명</span>
|
||||
</div>
|
||||
{/* 수요자 */}
|
||||
<div className="border border-gray-400">
|
||||
<div className="bg-gray-200 px-2 py-1 font-semibold text-center border-b border-gray-400">
|
||||
수 요 자
|
||||
</div>
|
||||
<table className="w-full text-xs">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1 w-20">업체명</td>
|
||||
<td className="border-b border-gray-300 px-2 py-1">{quoteData.clientName || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1">제품명</td>
|
||||
<td className="border-b border-gray-300 px-2 py-1">{locations[0]?.productCode || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1">현장명</td>
|
||||
<td className="border-b border-gray-300 px-2 py-1">{quoteData.siteName || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1">담당자</td>
|
||||
<td className="border-b border-gray-300 px-2 py-1">{quoteData.manager || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-r border-gray-300 bg-gray-50 px-2 py-1">연락처</td>
|
||||
<td className="px-2 py-1">{quoteData.contact || '-'}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 공급받는자 */}
|
||||
<div className="border border-gray-300">
|
||||
<div className="bg-gray-800 text-white p-2 text-sm font-medium text-center">
|
||||
공급받는자
|
||||
</div>
|
||||
<div className="p-3 space-y-1 text-sm">
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-600">상호</span>
|
||||
<span>{quoteData.clientName || '-'}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-600">담당자</span>
|
||||
<span>{quoteData.managerName || '-'}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-600">연락처</span>
|
||||
<span>{quoteData.contact || '-'}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-600">현장명</span>
|
||||
<span>{quoteData.siteName || '-'}</span>
|
||||
</div>
|
||||
{/* 공급자 */}
|
||||
<div className="border border-gray-400">
|
||||
<div className="bg-gray-200 px-2 py-1 font-semibold text-center border-b border-gray-400">
|
||||
공 급 자
|
||||
</div>
|
||||
<table className="w-full text-xs">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1 w-20">상호</td>
|
||||
<td colSpan={3} className="border-b border-gray-300 px-2 py-1">회사명</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1">등록번호</td>
|
||||
<td className="border-b border-r border-gray-300 px-2 py-1">123-12-12345</td>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1 w-16">대표자</td>
|
||||
<td className="border-b border-gray-300 px-2 py-1">홍길동</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1">사업장주소</td>
|
||||
<td colSpan={3} className="border-b border-gray-300 px-2 py-1">주소명</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1">업태</td>
|
||||
<td className="border-b border-r border-gray-300 px-2 py-1">제조업</td>
|
||||
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1">종목</td>
|
||||
<td className="border-b border-gray-300 px-2 py-1">방화셔터, 금속창호</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border-r border-gray-300 bg-gray-50 px-2 py-1">TEL</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1">031-123-1234</td>
|
||||
<td className="border-r border-gray-300 bg-gray-50 px-2 py-1">FAX</td>
|
||||
<td className="px-2 py-1">02-1234-1234</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 품목내역 */}
|
||||
<div className="border border-gray-300 mb-4">
|
||||
<div className="bg-gray-800 text-white p-2 text-sm font-medium text-center">
|
||||
품목내역
|
||||
{/* 내역 테이블 */}
|
||||
<div className="border border-gray-400 mb-4">
|
||||
<div className="bg-gray-800 text-white px-2 py-1 font-semibold text-center">
|
||||
내 역
|
||||
</div>
|
||||
<table className="w-full text-sm">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b border-gray-300">
|
||||
<th className="p-2 text-center font-medium border-r border-gray-300 w-12">순번</th>
|
||||
<th className="p-2 text-center font-medium border-r border-gray-300 w-28">품목코드</th>
|
||||
<th className="p-2 text-left font-medium border-r border-gray-300">품명</th>
|
||||
<th className="p-2 text-center font-medium border-r border-gray-300 w-24">규격</th>
|
||||
<th className="p-2 text-center font-medium border-r border-gray-300 w-12">수량</th>
|
||||
<th className="p-2 text-center font-medium border-r border-gray-300 w-12">단위</th>
|
||||
<th className="p-2 text-right font-medium border-r border-gray-300 w-24">단가</th>
|
||||
<th className="p-2 text-right font-medium w-24">공급가액</th>
|
||||
<tr className="bg-gray-100 border-b border-gray-400">
|
||||
<th className="border-r border-gray-300 px-2 py-1">No.</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" colSpan={2}>오픈사이즈</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>
|
||||
</tr>
|
||||
<tr className="bg-gray-50 border-b border-gray-300 text-[10px] text-gray-500">
|
||||
<th className="border-r border-gray-300 px-2 py-0.5"></th>
|
||||
<th className="border-r border-gray-300 px-2 py-0.5"></th>
|
||||
<th className="border-r border-gray-300 px-2 py-0.5"></th>
|
||||
<th className="border-r border-gray-300 px-2 py-0.5"></th>
|
||||
<th className="border-r border-gray-300 px-1 py-0.5">가로</th>
|
||||
<th className="border-r border-gray-300 px-1 py-0.5">세로</th>
|
||||
<th className="border-r border-gray-300 px-2 py-0.5"></th>
|
||||
<th className="border-r border-gray-300 px-2 py-0.5"></th>
|
||||
<th className="border-r border-gray-300 px-2 py-0.5"></th>
|
||||
<th className="px-2 py-0.5"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{locations.length > 0 ? (
|
||||
locations.map((location, index) => (
|
||||
<tr key={location.id} className="border-b border-gray-300">
|
||||
<td className="p-2 text-center border-r border-gray-300">{index + 1}</td>
|
||||
<td className="p-2 text-center border-r border-gray-300">{location.productCode || '-'}</td>
|
||||
<td className="p-2 border-r border-gray-300">{location.floor} / {location.symbol}</td>
|
||||
<td className="p-2 text-center border-r border-gray-300">{location.width}x{location.height}</td>
|
||||
<td className="p-2 text-center border-r border-gray-300">{location.quantity || 1}</td>
|
||||
<td className="p-2 text-center border-r border-gray-300">SET</td>
|
||||
<td className="p-2 text-right border-r border-gray-300">
|
||||
{(location.items || []).reduce((sum, item) => sum + (item.amount || 0), 0).toLocaleString()}
|
||||
locations.map((loc, index) => (
|
||||
<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.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 || 0}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.openHeight || 0}</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.quantity || 1}</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="p-2 text-right">
|
||||
{((location.items || []).reduce((sum, item) => sum + (item.amount || 0), 0) * (location.quantity || 1)).toLocaleString()}
|
||||
<td className="px-2 py-1 text-right">
|
||||
{(loc.totalPrice || 0).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr className="border-b border-gray-300">
|
||||
<td colSpan={8} className="p-4 text-center text-gray-400">
|
||||
<td colSpan={10} className="p-4 text-center text-gray-400">
|
||||
등록된 품목이 없습니다
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 금액 계산 */}
|
||||
<div className="border border-gray-300 mb-6">
|
||||
<table className="w-full text-sm">
|
||||
<tbody>
|
||||
<tfoot>
|
||||
{/* 소계 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="p-2 bg-gray-100 border-r border-gray-300 w-32">공급가액</td>
|
||||
<td className="p-2 text-right">{subtotal.toLocaleString()}원</td>
|
||||
<td colSpan={6} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
|
||||
소계
|
||||
</td>
|
||||
<td className="border-r border-gray-300 px-2 py-1 text-center">
|
||||
{locations.reduce((sum, loc) => sum + (loc.quantity || 0), 0)}
|
||||
</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">{subtotal.toLocaleString()}</td>
|
||||
</tr>
|
||||
{/* 할인 적용 시에만 표시 */}
|
||||
{(discountRate > 0 || discountAmount > 0) && (
|
||||
|
||||
{/* 할인율 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
|
||||
할인율
|
||||
</td>
|
||||
<td className="px-2 py-1 text-right">
|
||||
{hasDiscount ? `${discountRate.toFixed(1)} %` : '0.0 %'}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* 할인금액 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
|
||||
할인금액
|
||||
</td>
|
||||
<td className="px-2 py-1 text-right">
|
||||
{hasDiscount ? discountAmount.toLocaleString() : '0'}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* 할인 후 금액 */}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
|
||||
할인 후 금액
|
||||
</td>
|
||||
<td className="px-2 py-1 text-right">{afterDiscount.toLocaleString()}</td>
|
||||
</tr>
|
||||
|
||||
{/* 부가세 포함일 때 추가 행들 */}
|
||||
{vatIncluded && (
|
||||
<>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="p-2 bg-gray-100 border-r border-gray-300">할인율</td>
|
||||
<td className="p-2 text-right">{discountRate.toFixed(2)}%</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="p-2 bg-gray-100 border-r border-gray-300">할인액</td>
|
||||
<td className="p-2 text-right text-red-600">
|
||||
-{discountAmount.toLocaleString()}원
|
||||
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
|
||||
부가가치세 합계
|
||||
</td>
|
||||
<td className="px-2 py-1 text-right">{vat.toLocaleString()}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="p-2 bg-gray-100 border-r border-gray-300">할인 후 공급가액</td>
|
||||
<td className="p-2 text-right">{afterDiscount.toLocaleString()}원</td>
|
||||
<tr>
|
||||
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50 font-semibold">
|
||||
총 금액
|
||||
</td>
|
||||
<td className="px-2 py-1 text-right font-semibold">{grandTotal.toLocaleString()}</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
<tr className="border-b border-gray-300">
|
||||
<td className="p-2 bg-gray-100 border-r border-gray-300">부가세 (10%)</td>
|
||||
<td className="p-2 text-right">{vat.toLocaleString()}원</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="p-2 bg-gray-100 border-r border-gray-300 font-medium">합계 금액</td>
|
||||
<td className="p-2 text-right font-bold text-lg">₩ {finalTotal.toLocaleString()}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 증명 문구 */}
|
||||
<div className="text-center py-6 border-t border-gray-300">
|
||||
<p className="text-sm mb-4">위 금액을 거래하였음을 증명합니다.</p>
|
||||
<p className="text-sm text-gray-600 mb-4">{quoteData.receiptDate || today}</p>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-12 h-12 border-2 border-red-400 rounded-full flex items-center justify-center text-red-400 text-xs">
|
||||
{/* 합계금액 박스 */}
|
||||
<div className="border-2 border-gray-800 p-3 mb-4 flex justify-between items-center">
|
||||
<span className="font-semibold text-red-600">
|
||||
합계금액 ({vatIncluded ? '부가세 포함' : '부가세 별도'})
|
||||
</span>
|
||||
<span className="text-xl font-bold">
|
||||
₩ {grandTotal.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 증명 문구 + 인감 */}
|
||||
<div className="text-center py-4">
|
||||
<p className="text-sm mb-2">위와 같이 거래하였습니다.</p>
|
||||
<div className="flex justify-end items-center gap-2 pr-8">
|
||||
<span className="border border-red-400 rounded-full w-10 h-10 flex items-center justify-center text-red-400 text-xs">
|
||||
印
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentViewer>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user