summaryrefslogtreecommitdiff
path: root/lib/bidding/vendor
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/vendor')
-rw-r--r--lib/bidding/vendor/components/pr-items-pricing-table.tsx6
-rw-r--r--lib/bidding/vendor/partners-bidding-detail.tsx190
-rw-r--r--lib/bidding/vendor/partners-bidding-pre-quote.tsx51
3 files changed, 172 insertions, 75 deletions
diff --git a/lib/bidding/vendor/components/pr-items-pricing-table.tsx b/lib/bidding/vendor/components/pr-items-pricing-table.tsx
index 1dee7adb..13804251 100644
--- a/lib/bidding/vendor/components/pr-items-pricing-table.tsx
+++ b/lib/bidding/vendor/components/pr-items-pricing-table.tsx
@@ -286,6 +286,7 @@ export function PrItemsPricingTable({
<TableHead>단위</TableHead>
<TableHead>중량</TableHead>
<TableHead>중량단위</TableHead>
+ <TableHead>SHI 납품요청일</TableHead>
<TableHead>견적단가</TableHead>
<TableHead>견적금액</TableHead>
<TableHead>납품예정일</TableHead>
@@ -328,6 +329,11 @@ export function PrItemsPricingTable({
</TableCell>
<TableCell>{item.weightUnit || '-'}</TableCell>
<TableCell>
+ {item.requestedDeliveryDate ?
+ formatDate(item.requestedDeliveryDate, 'KR') : '-'
+ }
+ </TableCell>
+ <TableCell>
{readOnly ? (
<span className="font-medium">
{quotation.bidUnitPrice.toLocaleString()}
diff --git a/lib/bidding/vendor/partners-bidding-detail.tsx b/lib/bidding/vendor/partners-bidding-detail.tsx
index 8d24ca66..4b316eee 100644
--- a/lib/bidding/vendor/partners-bidding-detail.tsx
+++ b/lib/bidding/vendor/partners-bidding-detail.tsx
@@ -60,13 +60,13 @@ interface BiddingDetail {
content: string | null
contractType: string
biddingType: string
- awardCount: string
+ awardCount: string | null
contractPeriod: string | null
- preQuoteDate: string | null
- biddingRegistrationDate: string | null
- submissionStartDate: string | null
- submissionEndDate: string | null
- evaluationDate: string | null
+ preQuoteDate: Date | null
+ biddingRegistrationDate: Date | null
+ submissionStartDate: Date | null
+ submissionEndDate: Date | null
+ evaluationDate: Date | null
currency: string
budget: number | null
targetPrice: number | null
@@ -78,12 +78,12 @@ interface BiddingDetail {
biddingId: number
invitationStatus: string
finalQuoteAmount: number | null
- finalQuoteSubmittedAt: string | null
+ finalQuoteSubmittedAt: Date | null
isWinner: boolean
isAttendingMeeting: boolean | null
isBiddingParticipated: boolean | null
additionalProposals: string | null
- responseSubmittedAt: string | null
+ responseSubmittedAt: Date | null
}
interface PrItem {
@@ -101,11 +101,11 @@ interface PrItem {
hasSpecDocument: boolean | null
}
-interface PrItemQuotation {
+interface BiddingPrItemQuotation {
prItemId: number
bidUnitPrice: number
bidAmount: number
- proposedDeliveryDate?: string | null
+ proposedDeliveryDate?: string
technicalSpecification?: string
}
@@ -122,7 +122,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
// 품목별 견적 관련 상태
const [prItems, setPrItems] = React.useState<PrItem[]>([])
- const [prItemQuotations, setPrItemQuotations] = React.useState<PrItemQuotation[]>([])
+ const [prItemQuotations, setPrItemQuotations] = React.useState<BiddingPrItemQuotation[]>([])
const [totalQuotationAmount, setTotalQuotationAmount] = React.useState(0)
// 응찰 폼 상태
@@ -169,7 +169,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
prItemId: item.prItemId,
bidUnitPrice: item.bidUnitPrice,
bidAmount: item.bidAmount,
- proposedDeliveryDate: item.proposedDeliveryDate || '',
+ proposedDeliveryDate: item.proposedDeliveryDate || undefined,
technicalSpecification: item.technicalSpecification || undefined
}))
@@ -219,14 +219,43 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
if (result.success) {
toast({
- title: '성공',
- description: result.message,
+ title: participated ? '참여 확정' : '미참여 확정',
+ description: participated
+ ? '입찰에 참여하셨습니다. 이제 견적을 작성할 수 있습니다.'
+ : '입찰 참여를 거절하셨습니다.',
})
// 데이터 새로고침
const updatedDetail = await getBiddingDetailsForPartners(biddingId, companyId)
if (updatedDetail) {
setBiddingDetail(updatedDetail)
+
+ // 참여 확정 시 사전견적 데이터가 있다면 로드
+ if (participated && updatedDetail.biddingCompanyId) {
+ try {
+ const preQuoteData = await getSavedPrItemQuotations(updatedDetail.biddingCompanyId)
+ const convertedQuotations = preQuoteData.map(item => ({
+ prItemId: item.prItemId,
+ bidUnitPrice: item.bidUnitPrice,
+ bidAmount: item.bidAmount,
+ proposedDeliveryDate: item.proposedDeliveryDate || undefined,
+ technicalSpecification: item.technicalSpecification || undefined
+ }))
+
+ setPrItemQuotations(convertedQuotations)
+ const total = convertedQuotations.reduce((sum, q) => sum + q.bidAmount, 0)
+ setTotalQuotationAmount(total)
+
+ if (total > 0) {
+ setResponseData(prev => ({
+ ...prev,
+ finalQuoteAmount: total.toString()
+ }))
+ }
+ } catch (error) {
+ console.error('Failed to load pre-quote data after participation:', error)
+ }
+ }
}
} else {
toast({
@@ -248,7 +277,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
}
// 품목별 견적 변경 핸들러
- const handleQuotationsChange = (quotations: PrItemQuotation[]) => {
+ const handleQuotationsChange = (quotations: BiddingPrItemQuotation[]) => {
console.log('견적 변경:', quotations)
setPrItemQuotations(quotations)
}
@@ -282,7 +311,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
prItemId: q.prItemId,
bidUnitPrice: q.bidUnitPrice,
bidAmount: q.bidAmount,
- proposedDeliveryDate: q.proposedDeliveryDate || undefined,
+ proposedDeliveryDate: q.proposedDeliveryDate,
technicalSpecification: q.technicalSpecification
}))
@@ -367,7 +396,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
prItemId: q.prItemId,
bidUnitPrice: q.bidUnitPrice,
bidAmount: q.bidAmount,
- proposedDeliveryDate: q.proposedDeliveryDate || undefined,
+ proposedDeliveryDate: q.proposedDeliveryDate,
technicalSpecification: q.technicalSpecification
})) : undefined,
},
@@ -445,7 +474,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
<div className="flex items-center gap-2 mt-1">
<Badge variant="outline" className="font-mono">
{biddingDetail.biddingNumber}
- {biddingDetail.revision > 0 && ` Rev.${biddingDetail.revision}`}
+ {biddingDetail.revision && biddingDetail.revision > 0 && ` Rev.${biddingDetail.revision}`}
</Badge>
<Badge variant={
biddingDetail.status === 'bidding_disposal' ? 'destructive' :
@@ -460,24 +489,13 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
{/* 입찰 참여여부 상태 표시 */}
<div className="flex items-center gap-2">
- {biddingDetail.isBiddingParticipated === null ? (
- <div className="flex items-center gap-2">
- <Badge variant="outline">참여 결정 대기</Badge>
- <Button
- onClick={() => handleParticipationDecision(false)}
- disabled={isUpdatingParticipation}
- variant="destructive"
- size="sm"
- >
- <XCircle className="w-4 h-4 mr-1" />
- 미응찰
- </Button>
- </div>
- ) : (
- <Badge variant={biddingDetail.isBiddingParticipated ? 'default' : 'destructive'}>
- {biddingDetail.isBiddingParticipated ? '응찰' : '미응찰'}
- </Badge>
- )}
+ <Badge variant={
+ biddingDetail.isBiddingParticipated === null ? 'outline' :
+ biddingDetail.isBiddingParticipated === true ? 'default' : 'destructive'
+ }>
+ {biddingDetail.isBiddingParticipated === null ? '참여 결정 대기' :
+ biddingDetail.isBiddingParticipated === true ? '응찰' : '미응찰'}
+ </Badge>
</div>
</div>
@@ -516,10 +534,10 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
</div>
<div>
<Label className="text-sm font-medium text-muted-foreground">낙찰수</Label>
- <div className="mt-1">{biddingDetail.awardCount === 'single' ? '단수' : '복수'}</div>
+ <div className="mt-1">{biddingDetail.awardCount === 'single' ? '단수' : biddingDetail.awardCount === 'multiple' ? '복수' : '미설정'}</div>
</div>
<div>
- <Label className="text-sm font-medium text-muted-foreground">담당자</Label>
+ <Label className="text-sm font-medium text-muted-foreground">입찰 담당자</Label>
<div className="flex items-center gap-2 mt-1">
<User className="w-4 h-4" />
<span>{biddingDetail.managerName || '미설정'}</span>
@@ -527,7 +545,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
</div>
</div>
- {biddingDetail.budget && (
+ {/* {biddingDetail.budget && (
<div>
<Label className="text-sm font-medium text-muted-foreground">예산</Label>
<div className="flex items-center gap-2 mt-1">
@@ -535,7 +553,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
<span className="font-semibold">{formatCurrency(biddingDetail.budget)}</span>
</div>
</div>
- )}
+ )} */}
{/* 일정 정보 */}
<div className="pt-4 border-t">
@@ -560,33 +578,73 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
{/* 참여 상태에 따른 섹션 표시 */}
{biddingDetail.isBiddingParticipated === false ? (
/* 미응찰 상태 표시 */
- <Card>
- <CardHeader>
+ <Card>
+ <CardHeader>
<CardTitle className="flex items-center gap-2">
<XCircle className="w-5 h-5 text-destructive" />
입찰 참여 거절
</CardTitle>
- </CardHeader>
- <CardContent>
+ </CardHeader>
+ <CardContent>
<div className="text-center py-8">
<XCircle className="w-16 h-16 text-destructive mx-auto mb-4" />
<h3 className="text-lg font-semibold text-destructive mb-2">입찰에 참여하지 않기로 결정했습니다</h3>
<p className="text-muted-foreground">
해당 입찰에 대한 견적 제출 및 관련 기능은 이용할 수 없습니다.
</p>
- </div>
- </CardContent>
- </Card>
- ) : biddingDetail.isBiddingParticipated === true || biddingDetail.isBiddingParticipated === null ? (
+ </div>
+ </CardContent>
+ </Card>
+ ) : biddingDetail.isBiddingParticipated === null ? (
+ /* 참여 의사 확인 섹션 */
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Users className="w-5 h-5" />
+ 입찰 참여 의사 확인
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="text-center py-8">
+ <div className="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-4">
+ <Users className="w-8 h-8 text-primary" />
+ </div>
+ <h3 className="text-lg font-semibold mb-2">이 입찰에 참여하시겠습니까?</h3>
+ <p className="text-muted-foreground mb-6">
+ 참여를 선택하시면 견적 작성 및 제출이 가능합니다.
+ </p>
+ <div className="flex justify-center gap-4">
+ <Button
+ onClick={() => handleParticipationDecision(true)}
+ disabled={isUpdatingParticipation}
+ className="min-w-[120px]"
+ >
+ <CheckCircle className="w-4 h-4 mr-2" />
+ 참여하기
+ </Button>
+ <Button
+ onClick={() => handleParticipationDecision(false)}
+ disabled={isUpdatingParticipation}
+ variant="destructive"
+ className="min-w-[120px]"
+ >
+ <XCircle className="w-4 h-4 mr-2" />
+ 미참여
+ </Button>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ ) : biddingDetail.isBiddingParticipated === true ? (
/* 응찰 폼 섹션 */
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <Send className="w-5 h-5" />
- 응찰하기
- </CardTitle>
- </CardHeader>
- <CardContent className="space-y-6">
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Send className="w-5 h-5" />
+ 응찰하기
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-6">
{/* 품목별 견적 섹션 */}
{/* <div className="space-y-2">
<Label htmlFor="finalQuoteAmount">총 견적금액 *</Label>
@@ -648,24 +706,22 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
rows={4}
/>
</div> */}
- {/* 응찰 제출 버튼 - 미응찰 상태가 아닐 때만 표시 */}
- {(biddingDetail.isBiddingParticipated === true || biddingDetail.isBiddingParticipated === null) && (
+ {/* 응찰 제출 버튼 - 참여 확정 상태일 때만 표시 */}
<div className="flex justify-end pt-4 gap-2">
<Button
- variant="outline"
- onClick={handleSaveDraft}
- disabled={isSavingDraft || isSubmitting}
- className="min-w-[100px]"
- >
- <Save className="w-4 h-4 mr-2" />
- {isSavingDraft ? '저장 중...' : '임시 저장'}
- </Button>
+ variant="outline"
+ onClick={handleSaveDraft}
+ disabled={isSavingDraft || isSubmitting}
+ className="min-w-[100px]"
+ >
+ <Save className="w-4 h-4 mr-2" />
+ {isSavingDraft ? '저장 중...' : '임시 저장'}
+ </Button>
<Button onClick={handleSubmitResponse} disabled={isSubmitting || isSavingDraft} className="min-w-[100px]">
<Send className="w-4 h-4 mr-2" />
{isSubmitting ? '제출 중...' : '응찰 제출'}
</Button>
</div>
- )}
</CardContent>
</Card>
) : null}
diff --git a/lib/bidding/vendor/partners-bidding-pre-quote.tsx b/lib/bidding/vendor/partners-bidding-pre-quote.tsx
index fdd05154..29a37cae 100644
--- a/lib/bidding/vendor/partners-bidding-pre-quote.tsx
+++ b/lib/bidding/vendor/partners-bidding-pre-quote.tsx
@@ -381,6 +381,41 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
return
}
+ // 품목별 납품일 검증
+ if (prItemQuotations.length > 0) {
+ for (const quotation of prItemQuotations) {
+ if (!quotation.proposedDeliveryDate?.trim()) {
+ const prItem = prItems.find(item => item.id === quotation.prItemId)
+ toast({
+ title: '유효성 오류',
+ description: `품목 ${prItem?.itemNumber || quotation.prItemId}의 납품예정일을 입력해주세요.`,
+ variant: 'destructive',
+ })
+ return
+ }
+ }
+ }
+
+ const requiredFields = [
+ { value: responseData.proposedContractDeliveryDate, name: '제안 납품일' },
+ { value: responseData.paymentTermsResponse, name: '응답 지급조건' },
+ { value: responseData.taxConditionsResponse, name: '응답 세금조건' },
+ { value: responseData.incotermsResponse, name: '응답 운송조건' },
+ { value: responseData.proposedShippingPort, name: '제안 선적지' },
+ { value: responseData.proposedDestinationPort, name: '제안 도착지' },
+ { value: responseData.sparePartResponse, name: '스페어파트 응답' },
+ ]
+
+ const missingField = requiredFields.find(field => !field.value?.trim())
+ if (missingField) {
+ toast({
+ title: '유효성 오류',
+ description: `${missingField.name}을(를) 입력해주세요.`,
+ variant: 'destructive',
+ })
+ return
+ }
+
startTransition(async () => {
const submissionData = {
preQuoteAmount: totalAmount, // 품목별 계산된 총 금액 사용
@@ -873,7 +908,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
{/* 총 금액 표시 (읽기 전용) */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
- <Label htmlFor="totalAmount">총 사전견적 금액 *</Label>
+ <Label htmlFor="totalAmount">총 사전견적 금액 <span className="text-red-500">*</span></Label>
<Input
id="totalAmount"
type="text"
@@ -887,7 +922,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
</div>
<div className="space-y-2">
- <Label htmlFor="proposedContractDeliveryDate">제안 납품일</Label>
+ <Label htmlFor="proposedContractDeliveryDate">제안 납품일 <span className="text-red-500">*</span></Label>
<Input
id="proposedContractDeliveryDate"
type="date"
@@ -905,7 +940,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
- <Label htmlFor="paymentTermsResponse">응답 지급조건</Label>
+ <Label htmlFor="paymentTermsResponse">응답 지급조건 <span className="text-red-500">*</span></Label>
<Input
id="paymentTermsResponse"
value={responseData.paymentTermsResponse}
@@ -915,7 +950,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
</div>
<div className="space-y-2">
- <Label htmlFor="taxConditionsResponse">응답 세금조건</Label>
+ <Label htmlFor="taxConditionsResponse">응답 세금조건 <span className="text-red-500">*</span></Label>
<Input
id="taxConditionsResponse"
value={responseData.taxConditionsResponse}
@@ -927,7 +962,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
- <Label htmlFor="incotermsResponse">응답 운송조건</Label>
+ <Label htmlFor="incotermsResponse">응답 운송조건 <span className="text-red-500">*</span></Label>
<Input
id="incotermsResponse"
value={responseData.incotermsResponse}
@@ -937,7 +972,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
</div>
<div className="space-y-2">
- <Label htmlFor="proposedShippingPort">제안 선적지</Label>
+ <Label htmlFor="proposedShippingPort">제안 선적지 <span className="text-red-500">*</span></Label>
<Input
id="proposedShippingPort"
value={responseData.proposedShippingPort}
@@ -949,7 +984,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
- <Label htmlFor="proposedDestinationPort">제안 도착지</Label>
+ <Label htmlFor="proposedDestinationPort">제안 도착지 <span className="text-red-500">*</span></Label>
<Input
id="proposedDestinationPort"
value={responseData.proposedDestinationPort}
@@ -959,7 +994,7 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
</div>
<div className="space-y-2">
- <Label htmlFor="sparePartResponse">스페어파트 응답</Label>
+ <Label htmlFor="sparePartResponse">스페어파트 응답 <span className="text-red-500">*</span></Label>
<Input
id="sparePartResponse"
value={responseData.sparePartResponse}