Files
sam-react-prod/src/components/quotes/PurchaseOrderDocument.tsx
유병철 f344dc7d00 refactor(WEB): 회계/견적/설정/생산 등 전반적 코드 개선 및 공통화 2차
- 회계 모듈 전면 개선: 청구/입금/출금/매입/매출/세금계산서/일반전표/거래처원장 등
- 견적 모듈 금액 포맷/할인/수식/미리보기 등 코드 정리
- 설정 모듈: 계정관리/직급/직책/권한 상세 간소화
- 생산 모듈: 작업지시서/작업자화면/검수 문서 코드 정리
- UniversalListPage 엑셀 다운로드 및 필터 기능 확장
- 대시보드/게시판/수주 등 날짜 유틸 공통화 적용
- claudedocs 문서 인덱스 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:45:47 +09:00

266 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 발주서 (Purchase Order Document)
*
* 공통 컴포넌트 사용:
* - DocumentHeader: quote 레이아웃 + LotApprovalTable
*/
import { QuoteFormData } from "./types";
import type { CompanyFormData } from "@/components/settings/CompanyInfoManagement/types";
import { DocumentHeader, LotApprovalTable } from "@/components/document-system";
import { formatNumber } from '@/lib/utils/amount';
interface PurchaseOrderDocumentProps {
quote: QuoteFormData;
companyInfo?: CompanyFormData | null;
}
export function PurchaseOrderDocument({ quote, companyInfo }: PurchaseOrderDocumentProps) {
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}`;
};
// 발주번호 생성 (견적번호 기반)
const purchaseOrderNumber = quote.id?.replace('Q', 'KQ#-SC-') + '-01' || 'KQ#-SC-XXXXXX-01';
// BOM에서 부자재 목록 추출 (견적 품목 데이터 기반)
const materialItems = quote.items?.length > 0
? quote.items.map((item, index) => ({
no: index + 1,
name: item.productName || '스크린셔터',
spec: `${item.openWidth || 0}×${item.openHeight || 0}mm`,
length: Number(item.openHeight) || 0,
quantity: item.quantity || 1,
note: item.guideRailType ? `레일: ${item.guideRailType}` : ''
}))
: [];
return (
<>
<style>{`
@media print {
@page {
size: A4 portrait;
margin: 10mm;
}
body {
background: white !important;
}
#purchase-order-content {
background: white !important;
padding: 0 !important;
}
}
/* 발주서 공문서 스타일 */
.purchase-order {
font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif;
background: white;
color: #000;
line-height: 1.4;
}
/* 헤더 스타일은 공통 컴포넌트 사용 */
.po-section-table {
width: 100%;
border-collapse: collapse;
border: 2px solid #000;
margin-bottom: 15px;
}
.po-section-header {
background: #e8e8e8;
border: 1px solid #666;
padding: 8px;
text-align: center;
font-weight: 700;
font-size: 13px;
width: 120px;
}
.po-section-content {
border: 1px solid #999;
padding: 8px;
font-size: 12px;
}
.po-materials-table {
width: 100%;
border-collapse: collapse;
border: 2px solid #000;
margin-bottom: 20px;
}
.po-materials-table th {
background: #e8e8e8;
border: 1px solid #666;
padding: 8px;
text-align: center;
font-weight: 700;
font-size: 12px;
}
.po-materials-table td {
border: 1px solid #999;
padding: 8px;
font-size: 12px;
}
.po-notes {
margin-top: 20px;
padding: 15px;
background: #f9f9f9;
border: 1px solid #ddd;
font-size: 11px;
line-height: 1.6;
}
`}</style>
{/* 발주서 내용 */}
<div id="purchase-order-content" className="purchase-order p-12 print:p-8">
{/* 헤더: 제목 + 로트번호/결재란 (공통 컴포넌트) */}
<DocumentHeader
title="발 주 서"
layout="quote"
customApproval={
<LotApprovalTable
lotNumber={purchaseOrderNumber}
approvers={{
writer: { name: '전진', department: '판매/전진' },
reviewer: { name: '', department: '회계' },
approver: { name: '', department: '생산' },
}}
/>
}
/>
{/* 신청업체 */}
<table className="po-section-table">
<tbody>
<tr>
<th className="po-section-header" rowSpan={3}> </th>
<th className="po-section-header" style={{ width: '100px' }}></th>
<td className="po-section-content">{quote.clientName || '-'}</td>
<th className="po-section-header" style={{ width: '100px' }}></th>
<td className="po-section-content">{formatDate(quote.registrationDate || '')}</td>
</tr>
<tr>
<th className="po-section-header"></th>
<td className="po-section-content">{quote.manager || '-'}</td>
<th className="po-section-header"></th>
<td className="po-section-content">{quote.contact || '-'}</td>
</tr>
<tr>
<th className="po-section-header">F A X</th>
<td className="po-section-content">-</td>
<th className="po-section-header">()</th>
<td className="po-section-content">{quote.items?.reduce((sum, item) => sum + (item.quantity || 0), 0) || 0}</td>
</tr>
</tbody>
</table>
{/* 신청내용 */}
<table className="po-section-table">
<tbody>
<tr>
<th className="po-section-header" rowSpan={5}> </th>
<th className="po-section-header" style={{ width: '100px' }}></th>
<td className="po-section-content" colSpan={3}>{quote.siteName || '-'}</td>
</tr>
<tr>
<th className="po-section-header"></th>
<td className="po-section-content" colSpan={3}>{formatDate(quote.dueDate || '')}</td>
</tr>
<tr>
<th className="po-section-header"></th>
<td className="po-section-content">{formatDate(quote.registrationDate || '')}</td>
<th className="po-section-header" style={{ width: '100px' }}></th>
<td className="po-section-content"></td>
</tr>
<tr>
<th className="po-section-header"></th>
<td className="po-section-content" colSpan={3}> 16-180</td>
</tr>
<tr>
<th className="po-section-header"></th>
<td className="po-section-content">{quote.manager || '-'}</td>
<th className="po-section-header" style={{ width: '100px' }}></th>
<td className="po-section-content">{quote.contact || '-'}</td>
</tr>
</tbody>
</table>
{/* 발주개소 정보 */}
<table className="po-section-table">
<tbody>
<tr>
<th className="po-section-header" style={{ width: '120px' }}></th>
<td className="po-section-content" style={{ width: '200px' }}>-</td>
<th className="po-section-header" style={{ width: '120px' }}></th>
<td className="po-section-content">{quote.items?.reduce((sum, item) => sum + (item.quantity || 0), 0) || 0}</td>
</tr>
</tbody>
</table>
{/* 유의사항 */}
<div style={{ marginBottom: '10px', fontSize: '11px', lineHeight: '1.6' }}>
<p>1. .</p>
<p>2. .</p>
</div>
{/* 부자재 테이블 */}
<div style={{ fontWeight: '700', marginBottom: '10px', fontSize: '14px' }}> </div>
<table className="po-materials-table">
<thead>
<tr>
<th style={{ width: '50px' }}></th>
<th></th>
<th style={{ width: '200px' }}></th>
<th style={{ width: '100px' }}>(mm)</th>
<th style={{ width: '80px' }}></th>
<th style={{ width: '150px' }}></th>
</tr>
</thead>
<tbody>
{materialItems.map((item) => (
<tr key={item.no}>
<td style={{ textAlign: 'center' }}>{item.no}</td>
<td>{item.name}</td>
<td>{item.spec}</td>
<td style={{ textAlign: 'right' }}>{item.length > 0 ? formatNumber(item.length) : ''}</td>
<td style={{ textAlign: 'center', fontWeight: '600' }}>{item.quantity}</td>
<td>{item.note}</td>
</tr>
))}
</tbody>
</table>
{/* 특이사항 */}
<div style={{ marginBottom: '20px', padding: '10px', border: '1px solid #ddd', background: '#fafafa' }}>
<strong style={{ fontSize: '12px' }}></strong>
<div style={{ fontSize: '11px', marginTop: '5px', lineHeight: '1.6' }}>
{quote.remarks || '스크린 셔터 부품구성표 기반 자동 견적'}
</div>
</div>
{/* 하단 안내사항 */}
<div className="po-notes">
<p style={{ fontWeight: '600', marginBottom: '5px' }}> </p>
<p> .</p>
<p> , .</p>
<p> .</p>
<p style={{ marginTop: '10px', textAlign: 'center', fontWeight: '600' }}>
: {companyInfo?.managerName || quote.writer || '담당자'} | {companyInfo?.managerPhone || '-'}
</p>
</div>
</div>
</>
);
}