summaryrefslogtreecommitdiff
path: root/lib/bidding/vendor
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-27 03:08:50 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-27 03:08:50 +0000
commit79cfa7ea8f21ae227dbb2843ae536fe876ba7c55 (patch)
treef12efae72c62286c1a2e9a3f31d695ca22d83b6e /lib/bidding/vendor
parente1da84ac863989b9f63b089c09aaa2bbcdc3d6cd (diff)
(최겸) 구매 입찰 수정
Diffstat (limited to 'lib/bidding/vendor')
-rw-r--r--lib/bidding/vendor/partners-bidding-detail.tsx234
1 files changed, 124 insertions, 110 deletions
diff --git a/lib/bidding/vendor/partners-bidding-detail.tsx b/lib/bidding/vendor/partners-bidding-detail.tsx
index 10fe71a9..03429cca 100644
--- a/lib/bidding/vendor/partners-bidding-detail.tsx
+++ b/lib/bidding/vendor/partners-bidding-detail.tsx
@@ -32,10 +32,11 @@ import {
submitPartnerResponse,
updatePartnerBiddingParticipation,
saveBiddingDraft,
- getPriceAdjustmentFormByBiddingCompanyId
+ getPriceAdjustmentFormByBiddingCompanyId,
+ getPartnerBiddingItemQuotations
} from '../detail/service'
import { cancelBiddingResponse } from '../detail/bidding-actions'
-import { getPrItemsForBidding, getSavedPrItemQuotations } from '@/lib/bidding/pre-quote/service'
+import { getPrItemsForBidding } from '@/lib/bidding/pre-quote/service'
import { getBiddingConditions } from '@/lib/bidding/service'
import { PrItemsPricingTable } from './components/pr-items-pricing-table'
import { SimpleFileUpload } from './components/simple-file-upload'
@@ -68,13 +69,13 @@ interface BiddingDetail {
contractType: string
biddingType: string
awardCount: string | null
- contractStartDate: Date | null
- contractEndDate: Date | null
- preQuoteDate: Date | null
- biddingRegistrationDate: Date | null
- submissionStartDate: Date | null
- submissionEndDate: Date | null
- evaluationDate: Date | null
+ contractStartDate: Date | string | null
+ contractEndDate: Date | string | null
+ preQuoteDate: Date | string | null
+ biddingRegistrationDate: Date | string | null
+ submissionStartDate: Date | string | null
+ submissionEndDate: Date | string | null
+ evaluationDate: Date | string | null
currency: string
budget: number | null
targetPrice: number | null
@@ -109,7 +110,7 @@ interface PrItem {
materialGroupInfo: string | null
materialNumber: string | null
materialInfo: string | null
- requestedDeliveryDate: Date | null
+ requestedDeliveryDate: Date | string | null
annualUnitPrice: string | null
currency: string | null
quantity: string | null
@@ -160,16 +161,17 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
const [isCancelling, setIsCancelling] = React.useState(false)
const [isFinalSubmission, setIsFinalSubmission] = React.useState(false)
const [isBiddingNoticeLoading, setIsBiddingNoticeLoading] = React.useState(false)
+ const [isExpired, setIsExpired] = React.useState(false)
// 입찰공고 관련 상태
const [biddingNotice, setBiddingNotice] = React.useState<{
id?: number
- biddingId?: number
+ biddingId?: number | null
title?: string
content?: string
isTemplate?: boolean
- createdAt?: string
- updatedAt?: string
+ createdAt?: string | Date
+ updatedAt?: string | Date
} | null>(null)
const [biddingConditions, setBiddingConditions] = React.useState<BiddingConditions | null>(null)
const [isNoticeOpen, setIsNoticeOpen] = React.useState(false)
@@ -274,6 +276,13 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
})
setBiddingDetail(result)
+
+ // 만료 여부 확인
+ if (result.submissionEndDate) {
+ const now = new Date()
+ const deadline = new Date(result.submissionEndDate)
+ setIsExpired(deadline < now)
+ }
// 기존 응답 데이터로 폼 초기화
setResponseData({
@@ -297,7 +306,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
if (result?.biddingCompanyId) {
try {
// 입찰 데이터를 가져와서 본입찰용으로 변환
- const preQuoteData = await getSavedPrItemQuotations(result.biddingCompanyId)
+ const preQuoteData = await getPartnerBiddingItemQuotations(result.biddingCompanyId)
if (preQuoteData && Array.isArray(preQuoteData) && preQuoteData.length > 0) {
console.log('입찰 데이터:', preQuoteData)
@@ -325,19 +334,19 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
}, 0)
setTotalQuotationAmount(total)
console.log('계산된 총 금액:', total)
+
+ // 응찰 확정 시에만 입찰 금액을 finalQuoteAmount로 설정
+ if (total > 0 && result?.isBiddingParticipated === true) {
+ console.log('응찰 확정됨, 입찰 금액 설정:', total)
+ console.log('입찰 금액을 finalQuoteAmount로 설정:', total)
+ setResponseData(prev => ({
+ ...prev,
+ finalQuoteAmount: total.toString()
+ }))
+ }
}
}
- // 응찰 확정 시에만 입찰 금액을 finalQuoteAmount로 설정
- if (totalQuotationAmount > 0 && result?.isBiddingParticipated === true) {
- console.log('응찰 확정됨, 입찰 금액 설정:', totalQuotationAmount)
- console.log('입찰 금액을 finalQuoteAmount로 설정:', totalQuotationAmount)
- setResponseData(prev => ({
- ...prev,
- finalQuoteAmount: totalQuotationAmount.toString()
- }))
- }
-
// 연동제 데이터 로드 (입찰에서 답변했으면 로드, 아니면 입찰 조건 확인)
if (result.priceAdjustmentResponse !== null) {
// 입찰에서 이미 답변한 경우 - 연동제 폼 로드
@@ -385,6 +394,16 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
const handleParticipationDecision = async (participated: boolean) => {
if (!biddingDetail) return
+ // 만료 체크
+ if (isExpired) {
+ toast({
+ title: "참여 불가",
+ description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.",
+ variant: "destructive",
+ })
+ return
+ }
+
setIsUpdatingParticipation(true)
try {
const result = await updatePartnerBiddingParticipation(
@@ -409,7 +428,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
// 참여 확정 시 입찰 데이터가 있다면 로드
if (participated && updatedDetail.biddingCompanyId) {
try {
- const preQuoteData = await getSavedPrItemQuotations(updatedDetail.biddingCompanyId)
+ const preQuoteData = await getPartnerBiddingItemQuotations(updatedDetail.biddingCompanyId)
if (preQuoteData && Array.isArray(preQuoteData) && preQuoteData.length > 0) {
console.log('참여확정 후 입찰 데이터:', preQuoteData)
@@ -487,18 +506,14 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
const handleSaveDraft = async () => {
if (!biddingDetail || !userId) return
- // 제출 마감일 체크
- if (biddingDetail.submissionEndDate) {
- const now = new Date()
- const deadline = new Date(biddingDetail.submissionEndDate)
- if (deadline < now) {
- toast({
- title: "접근 제한",
- description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.",
- variant: "destructive",
- })
- return
- }
+ // 제출 마감일 체크 (상태 사용)
+ if (isExpired) {
+ toast({
+ title: "접근 제한",
+ description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.",
+ variant: "destructive",
+ })
+ return
}
// 입찰 마감 상태 체크
@@ -566,7 +581,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
}
}
- // 응찰 취소 핸들러
+ // 응찰 포기 핸들러
const handleCancelResponse = async () => {
if (!biddingDetail || !userId) return
@@ -590,7 +605,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
if (result.success) {
toast({
- title: '응찰 취소 완료',
+ title: '응찰 포기 완료',
description: '응찰이 취소되었습니다.',
})
// 페이지 새로고침
@@ -602,7 +617,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
}
} else {
toast({
- title: '응찰 취소 실패',
+ title: '응찰 포기 실패',
description: result.error,
variant: 'destructive',
})
@@ -611,7 +626,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
console.error('Failed to cancel bidding response:', error)
toast({
title: '오류',
- description: '응찰 취소에 실패했습니다.',
+ description: '응찰 포기에 실패했습니다.',
variant: 'destructive',
})
} finally {
@@ -622,18 +637,14 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
const handleSubmitResponse = () => {
if (!biddingDetail) return
- // 제출 마감일 체크
- if (biddingDetail.submissionEndDate) {
- const now = new Date()
- const deadline = new Date(biddingDetail.submissionEndDate)
- if (deadline < now) {
- toast({
- title: "접근 제한",
- description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.",
- variant: "destructive",
- })
- return
- }
+ // 제출 마감일 체크 (상태 사용)
+ if (isExpired) {
+ toast({
+ title: "접근 제한",
+ description: "제출 마감일이 지났습니다. 더 이상 입찰에 참여할 수 없습니다.",
+ variant: "destructive",
+ })
+ return
}
// 입찰 마감 상태 체크
@@ -692,8 +703,11 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
additionalProposals: responseData.additionalProposals,
isFinalSubmission, // 최종제출 여부 추가
// 연동제 데이터 추가 (연동제 적용요건 문의가 있는 경우만)
- priceAdjustmentResponse: biddingDetail.isPriceAdjustmentApplicableQuestion ? responseData.priceAdjustmentResponse : undefined,
- priceAdjustmentForm: biddingDetail.isPriceAdjustmentApplicableQuestion && responseData.priceAdjustmentResponse !== null ? priceAdjustmentForm : undefined,
+ priceAdjustmentResponse: biddingDetail.isPriceAdjustmentApplicableQuestion ? (responseData.priceAdjustmentResponse ?? false) as boolean : false,
+ priceAdjustmentForm: biddingDetail.isPriceAdjustmentApplicableQuestion && (responseData.priceAdjustmentResponse ?? false) as boolean ? {
+ ...priceAdjustmentForm,
+ adjustmentRatio: parseFloat(priceAdjustmentForm.adjustmentRatio) || 0
+ } : undefined,
prItemQuotations: prItemQuotations.length > 0 ? prItemQuotations.map(q => ({
prItemId: q.prItemId,
bidUnitPrice: q.bidUnitPrice,
@@ -849,57 +863,57 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
{/* 제출 마감일 D-day */}
- {biddingDetail.submissionEndDate && (
- <div className="pt-4 border-t">
- <Label className="text-sm font-medium text-muted-foreground mb-2 block">제출 마감 정보</Label>
- {(() => {
- const now = new Date()
- const deadline = new Date(biddingDetail.submissionEndDate.toISOString().slice(0, 16).replace('T', ' '))
- const isExpired = deadline < now
- const timeLeft = deadline.getTime() - now.getTime()
- const daysLeft = Math.floor(timeLeft / (1000 * 60 * 60 * 24))
- const hoursLeft = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
-
- return (
- <div className={`p-3 rounded-lg border-2 ${
- isExpired
- ? 'border-red-200 bg-red-50'
- : daysLeft <= 1
- ? 'border-orange-200 bg-orange-50'
- : 'border-green-200 bg-green-50'
- }`}>
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-2">
- <Calendar className="w-5 h-5" />
- <span className="font-medium">제출 마감일:</span>
- <span className="text-lg font-semibold">
- {biddingDetail.submissionEndDate.toISOString().slice(0, 16).replace('T', ' ')}
- </span>
+ {biddingDetail.submissionEndDate && (
+ <div className="pt-4 border-t">
+ <Label className="text-sm font-medium text-muted-foreground mb-2 block">제출 마감 정보</Label>
+ {(() => {
+ const now = new Date()
+ const deadline = new Date(biddingDetail.submissionEndDate.toISOString().slice(0, 16).replace('T', ' '))
+ // isExpired 상태 사용
+ const timeLeft = deadline.getTime() - now.getTime()
+ const daysLeft = Math.floor(timeLeft / (1000 * 60 * 60 * 24))
+ const hoursLeft = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
+
+ return (
+ <div className={`p-3 rounded-lg border-2 ${
+ isExpired
+ ? 'border-red-200 bg-red-50'
+ : daysLeft <= 1
+ ? 'border-orange-200 bg-orange-50'
+ : 'border-green-200 bg-green-50'
+ }`}>
+ <div className="flex items-center justify-between">
+ <div className="flex items-center gap-2">
+ <Calendar className="w-5 h-5" />
+ <span className="font-medium">제출 마감일:</span>
+ <span className="text-lg font-semibold">
+ {biddingDetail.submissionEndDate.toISOString().slice(0, 16).replace('T', ' ')}
+ </span>
+ </div>
+ {isExpired ? (
+ <Badge variant="destructive" className="ml-2">
+ 마감됨
+ </Badge>
+ ) : daysLeft <= 1 ? (
+ <Badge variant="secondary" className="ml-2 bg-orange-100 text-orange-800">
+ {daysLeft === 0 ? `${hoursLeft}시간 남음` : `${daysLeft}일 남음`}
+ </Badge>
+ ) : (
+ <Badge variant="secondary" className="ml-2 bg-green-100 text-green-800">
+ {daysLeft}일 남음
+ </Badge>
+ )}
</div>
- {isExpired ? (
- <Badge variant="destructive" className="ml-2">
- 마감됨
- </Badge>
- ) : daysLeft <= 1 ? (
- <Badge variant="secondary" className="ml-2 bg-orange-100 text-orange-800">
- {daysLeft === 0 ? `${hoursLeft}시간 남음` : `${daysLeft}일 남음`}
- </Badge>
- ) : (
- <Badge variant="secondary" className="ml-2 bg-green-100 text-green-800">
- {daysLeft}일 남음
- </Badge>
+ {isExpired && (
+ <div className="mt-2 text-sm text-red-600">
+ ⚠️ 제출 마감일이 지났습니다. 입찰 제출이 불가능합니다.
+ </div>
)}
</div>
- {isExpired && (
- <div className="mt-2 text-sm text-red-600">
- ⚠️ 제출 마감일이 지났습니다. 입찰 제출이 불가능합니다.
- </div>
- )}
- </div>
- )
- })()}
- </div>
- )}
+ )
+ })()}
+ </div>
+ )}
{/* 일정 정보 */}
<div className="pt-4 border-t">
@@ -1086,15 +1100,15 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
<div className="flex justify-center gap-4">
<Button
onClick={() => handleParticipationDecision(true)}
- disabled={isUpdatingParticipation}
+ disabled={isUpdatingParticipation || isExpired}
className="min-w-[120px]"
>
<CheckCircle className="w-4 h-4 mr-2" />
- 참여하기
+ {isExpired ? '마감됨' : '참여하기'}
</Button>
<Button
onClick={() => handleParticipationDecision(false)}
- disabled={isUpdatingParticipation}
+ disabled={isUpdatingParticipation || isExpired}
variant="destructive"
className="min-w-[120px]"
>
@@ -1407,7 +1421,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
{/* 응찰 제출 버튼 - 참여 확정 상태일 때만 표시 */}
<div className="flex justify-between pt-4 gap-2">
- {/* 응찰 취소 버튼 (최종제출 아닌 경우만) */}
+ {/* 응찰 포기 버튼 (최종제출 아닌 경우만) */}
{biddingDetail.finalQuoteSubmittedAt && !biddingDetail.isFinalSubmission && (
<Button
variant="destructive"
@@ -1416,14 +1430,14 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
className="min-w-[100px]"
>
<Trash2 className="w-4 h-4 mr-2" />
- {isCancelling ? '취소 중...' : '응찰 취소'}
+ {isCancelling ? '취소 중...' : '응찰 포기'}
</Button>
)}
<div className="flex gap-2 ml-auto">
<Button
variant="outline"
onClick={handleSaveDraft}
- disabled={isSavingDraft || isSubmitting || biddingDetail.isFinalSubmission}
+ disabled={isSavingDraft || isSubmitting || biddingDetail.isFinalSubmission || isExpired}
className="min-w-[100px]"
>
<Save className="w-4 h-4 mr-2" />
@@ -1431,7 +1445,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
</Button>
<Button
onClick={handleSubmitResponse}
- disabled={isSubmitting || isSavingDraft || biddingDetail.isFinalSubmission}
+ disabled={isSubmitting || isSavingDraft || biddingDetail.isFinalSubmission || isExpired}
className="min-w-[100px]"
>
<Send className="w-4 h-4 mr-2" />