summaryrefslogtreecommitdiff
path: root/lib/bidding/detail
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-18 05:26:47 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-18 05:26:47 +0000
commit6a8d6cda345d13352961d40abd44cfa5daf81591 (patch)
treed675bdaddbf4a518e26673f256afb7aa29686b19 /lib/bidding/detail
parent53fce4bf4ac8310bd02d77e564f28d3c12228bd1 (diff)
(최겸) 구매 입찰 수정, PCR 수정
Diffstat (limited to 'lib/bidding/detail')
-rw-r--r--lib/bidding/detail/service.ts225
-rw-r--r--lib/bidding/detail/table/bidding-detail-content.tsx6
-rw-r--r--lib/bidding/detail/table/bidding-detail-header.tsx103
-rw-r--r--lib/bidding/detail/table/bidding-detail-items-dialog.tsx1
-rw-r--r--lib/bidding/detail/table/bidding-detail-vendor-columns.tsx25
-rw-r--r--lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx9
-rw-r--r--lib/bidding/detail/table/bidding-detail-vendor-table.tsx69
-rw-r--r--lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx11
-rw-r--r--lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx2
9 files changed, 16 insertions, 435 deletions
diff --git a/lib/bidding/detail/service.ts b/lib/bidding/detail/service.ts
index a603834c..9fb3d87f 100644
--- a/lib/bidding/detail/service.ts
+++ b/lib/bidding/detail/service.ts
@@ -1,7 +1,7 @@
'use server'
import db from '@/db/db'
-import { biddings, prItemsForBidding, biddingDocuments, biddingCompanies, vendors, companyPrItemBids, companyConditionResponses, vendorSelectionResults, BiddingListItem, biddingConditions, priceAdjustmentForms, users } from '@/db/schema'
+import { biddings, prItemsForBidding, biddingDocuments, biddingCompanies, vendors, companyPrItemBids, companyConditionResponses, vendorSelectionResults, priceAdjustmentForms, users } from '@/db/schema'
import { specificationMeetings } from '@/db/schema/bidding'
import { eq, and, sql, desc, ne } from 'drizzle-orm'
import { revalidatePath, revalidateTag } from 'next/cache'
@@ -34,7 +34,7 @@ export interface BiddingDetailData {
}
// getBiddingById 함수 임포트 (기존 함수 재사용)
-import { getBiddingById, getPRDetailsAction } from '@/lib/bidding/service'
+import { getBiddingById } from '@/lib/bidding/service'
// Promise.all을 사용하여 모든 데이터를 병렬로 조회 (캐시 적용)
export async function getBiddingDetailData(biddingId: number): Promise<BiddingDetailData> {
@@ -674,216 +674,6 @@ export async function createBiddingDetailVendor(
}
}
-// 협력업체 정보 저장 - biddingCompanies와 companyConditionResponses 테이블에 레코드 생성
-export async function createQuotationVendor(input: any, userId: string) {
- try {
- const userName = await getUserNameById(userId)
- const result = await db.transaction(async (tx) => {
- // 0. 중복 체크 - 이미 해당 입찰에 참여중인 업체인지 확인
- const existingCompany = await tx
- .select()
- .from(biddingCompanies)
- .where(sql`${biddingCompanies.biddingId} = ${input.biddingId} AND ${biddingCompanies.companyId} = ${input.companyId}`)
-
- if (existingCompany.length > 0) {
- throw new Error('이미 등록된 업체입니다')
- }
-
- // 1. biddingCompanies에 레코드 생성
- const biddingCompanyResult = await tx.insert(biddingCompanies).values({
- biddingId: input.biddingId,
- companyId: input.vendorId,
- finalQuoteAmount: input.quotationAmount?.toString(),
- awardRatio: input.awardRatio?.toString(),
- isWinner: null, // 미정 상태로 초기화
- contactPerson: input.contactPerson,
- contactEmail: input.contactEmail,
- contactPhone: input.contactPhone,
- finalQuoteSubmittedAt: new Date(),
- // 스키마에 createdBy, updatedBy 필드가 없으므로 제거
- }).returning({ id: biddingCompanies.id })
-
- if (biddingCompanyResult.length === 0) {
- throw new Error('협력업체 정보 저장에 실패했습니다.')
- }
-
- const biddingCompanyId = biddingCompanyResult[0].id
-
- // 2. companyConditionResponses에 입찰 조건 생성
- await tx.insert(companyConditionResponses).values({
- biddingCompanyId: biddingCompanyId,
- paymentTermsResponse: input.paymentTermsResponse || '',
- taxConditionsResponse: input.taxConditionsResponse || '',
- proposedContractDeliveryDate: input.proposedContractDeliveryDate || null,
- priceAdjustmentResponse: input.priceAdjustmentResponse || false,
- incotermsResponse: input.incotermsResponse || '',
- proposedShippingPort: input.proposedShippingPort || '',
- proposedDestinationPort: input.proposedDestinationPort || '',
- sparePartResponse: input.sparePartResponse || '',
- additionalProposals: input.additionalProposals || '',
- isPreQuote: false,
- submittedAt: new Date(),
- createdAt: new Date(),
- updatedAt: new Date(),
- })
-
- return biddingCompanyId
- })
-
- // 캐시 무효화
- revalidateTag(`bidding-${input.biddingId}`)
- revalidateTag('quotation-vendors')
- revalidateTag('quotation-details')
- revalidatePath(`/evcp/bid/[id]`)
- return {
- success: true,
- message: '협력업체 정보가 성공적으로 저장되었습니다.',
- data: { id: result }
- }
- } catch (error) {
- console.error('Failed to create quotation vendor:', error)
- return { success: false, error: '협력업체 정보 저장에 실패했습니다.' }
- }
-}
-
-// 협력업체 정보 업데이트
-export async function updateQuotationVendor(id: number, input: any, userId: string) {
- try {
- const userName = await getUserNameById(userId)
- const result = await db.transaction(async (tx) => {
- // 1. biddingCompanies 테이블 업데이트
- const updateData: any = {}
- if (input.quotationAmount !== undefined) updateData.finalQuoteAmount = input.quotationAmount
- if (input.contactPerson !== undefined) updateData.contactPerson = input.contactPerson
- if (input.contactEmail !== undefined) updateData.contactEmail = input.contactEmail
- if (input.contactPhone !== undefined) updateData.contactPhone = input.contactPhone
- if (input.awardRatio !== undefined) updateData.awardRatio = input.awardRatio?.toString()
- // status 필드가 스키마에 없으므로 제거
- // updatedBy 필드가 스키마에 없으므로 제거
- updateData.updatedAt = new Date()
-
- if (Object.keys(updateData).length > 0) {
- await tx.update(biddingCompanies)
- .set(updateData)
- .where(eq(biddingCompanies.id, id))
- }
-
- // 2. companyConditionResponses 테이블 업데이트 (입찰 조건들)
- if (input.paymentTermsResponse !== undefined ||
- input.taxConditionsResponse !== undefined ||
- input.incotermsResponse !== undefined ||
- input.proposedContractDeliveryDate !== undefined ||
- input.proposedShippingPort !== undefined ||
- input.proposedDestinationPort !== undefined ||
- input.priceAdjustmentResponse !== undefined ||
- input.sparePartResponse !== undefined ||
- input.additionalProposals !== undefined) {
-
- const conditionsUpdateData: any = {}
- if (input.paymentTermsResponse !== undefined) conditionsUpdateData.paymentTermsResponse = input.paymentTermsResponse
- if (input.taxConditionsResponse !== undefined) conditionsUpdateData.taxConditionsResponse = input.taxConditionsResponse
- if (input.incotermsResponse !== undefined) conditionsUpdateData.incotermsResponse = input.incotermsResponse
- if (input.proposedContractDeliveryDate !== undefined) conditionsUpdateData.proposedContractDeliveryDate = input.proposedContractDeliveryDate || null
- if (input.proposedShippingPort !== undefined) conditionsUpdateData.proposedShippingPort = input.proposedShippingPort
- if (input.proposedDestinationPort !== undefined) conditionsUpdateData.proposedDestinationPort = input.proposedDestinationPort
- if (input.priceAdjustmentResponse !== undefined) conditionsUpdateData.priceAdjustmentResponse = input.priceAdjustmentResponse
- if (input.sparePartResponse !== undefined) conditionsUpdateData.sparePartResponse = input.sparePartResponse
- if (input.additionalProposals !== undefined) conditionsUpdateData.additionalProposals = input.additionalProposals
- conditionsUpdateData.updatedAt = new Date()
-
- await tx.update(companyConditionResponses)
- .set(conditionsUpdateData)
- .where(eq(companyConditionResponses.biddingCompanyId, id))
- }
-
- return true
- })
-
- // 캐시 무효화 (모든 입찰 관련 데이터 무효화)
- revalidateTag('quotation-vendors')
- revalidateTag('quotation-details')
- revalidatePath(`/evcp/bid/[id]`)
- return {
- success: true,
- message: '협력업체 정보가 성공적으로 업데이트되었습니다.',
- }
- } catch (error) {
- console.error('Failed to update quotation vendor:', error)
- return { success: false, error: '협력업체 정보 업데이트에 실패했습니다.' }
- }
-}
-
-// 협력업체 정보 삭제
-export async function deleteQuotationVendor(id: number) {
- try {
- // TODO: 실제로는 견적 시스템의 테이블에서 삭제
- console.log(`[TODO] 견적 시스템에서 협력업체 정보 ${id} 삭제 예정`)
-
- // 임시로 성공 응답
- return { success: true, message: '협력업체 정보가 성공적으로 삭제되었습니다.' }
- } catch (error) {
- console.error('Failed to delete quotation vendor:', error)
- return { success: false, error: '협력업체 정보 삭제에 실패했습니다.' }
- }
-}
-
-// 낙찰 처리
-export async function selectWinner(biddingId: number, vendorId: number, awardRatio: number, userId: string) {
- try {
- // 트랜잭션으로 처리
- await db.transaction(async (tx) => {
- // 기존 낙찰자 초기화
- await tx
- .update(biddingCompanies)
- .set({
- isWinner: false,
- updatedAt: new Date()
- })
- .where(eq(biddingCompanies.biddingId, biddingId))
-
- // 새로운 낙찰자 설정
- const biddingCompany = await tx
- .select()
- .from(biddingCompanies)
- .where(and(
- eq(biddingCompanies.biddingId, biddingId),
- eq(biddingCompanies.companyId, vendorId)
- ))
- .limit(1)
-
- if (biddingCompany.length > 0) {
- await tx
- .update(biddingCompanies)
- .set({
- isWinner: true,
- updatedAt: new Date()
- })
- .where(eq(biddingCompanies.id, biddingCompany[0].id))
- }
-
- // biddings 테이블의 상태 업데이트
- await tx
- .update(biddings)
- .set({
- status: 'vendor_selected',
- finalBidPrice: undefined, // TODO: 낙찰가 설정 로직 추가
- updatedAt: new Date()
- })
- .where(eq(biddings.id, biddingId))
- })
-
- // 캐시 무효화
- revalidateTag(`bidding-${biddingId}`)
- revalidateTag('quotation-vendors')
- revalidateTag('quotation-details')
- revalidatePath(`/evcp/bid/${biddingId}`)
- return { success: true, message: '낙찰 처리가 완료되었습니다.' }
- } catch (error) {
- console.error('Failed to select winner:', error)
- return { success: false, error: '낙찰 처리에 실패했습니다.' }
- }
-}
-
// 유찰 처리
export async function markAsDisposal(biddingId: number, userId: string) {
try {
@@ -913,12 +703,14 @@ export async function markAsDisposal(biddingId: number, userId: string) {
eq(biddingCompanies.biddingId, biddingId),
eq(biddingCompanies.isBiddingParticipated, true)
))
+ const userName = await getUserNameById(userId)
// 입찰 상태를 유찰로 변경
await db
.update(biddings)
.set({
status: 'bidding_disposal',
+ updatedBy: userName,
updatedAt: new Date()
})
.where(eq(biddings.id, biddingId))
@@ -996,12 +788,15 @@ export async function registerBidding(biddingId: number, userId: string) {
const bidding = biddingInfo[0]
+ const userName = await getUserNameById(userId)
+
await db.transaction(async (tx) => {
// 1. 입찰 상태를 오픈으로 변경
await tx
.update(biddings)
.set({
status: 'bidding_opened',
+ updatedBy: userName,
updatedAt: new Date()
})
.where(eq(biddings.id, biddingId))
@@ -1098,13 +893,14 @@ export async function createRebidding(biddingId: number, userId: string) {
eq(biddingCompanies.biddingId, biddingId),
eq(biddingCompanies.isBiddingParticipated, true)
))
-
+ const userName = await getUserNameById(userId)
// 기존 입찰의 revision 증가 및 상태 변경
const updatedBidding = await db
.update(biddings)
.set({
revision: (originalBidding.revision || 0) + 1,
status: 'bidding_opened', // 재입찰 시 다시 오픈 상태로
+ updatedBy: userName,
updatedAt: new Date()
})
.where(eq(biddings.id, biddingId))
@@ -1721,6 +1517,7 @@ export interface PartnersBiddingListItem {
submissionDate: Date | null // 입찰제출일 (submissionEndDate)
}
+// 협력업체용 입찰 목록 조회 (bidding_companies 기준)
export async function getBiddingListForPartners(companyId: number): Promise<PartnersBiddingListItem[]> {
try {
const result = await db
@@ -1821,7 +1618,6 @@ export async function getBiddingDetailsForPartners(biddingId: number, companyId:
contractEndDate: biddings.contractEndDate,
// 일정 정보
- preQuoteDate: biddings.preQuoteDate,
biddingRegistrationDate: biddings.biddingRegistrationDate,
submissionStartDate: biddings.submissionStartDate,
submissionEndDate: biddings.submissionEndDate,
@@ -1849,7 +1645,6 @@ export async function getBiddingDetailsForPartners(biddingId: number, companyId:
isPreQuoteSelected: biddingCompanies.isPreQuoteSelected,
isBiddingParticipated: biddingCompanies.isBiddingParticipated,
isPreQuoteParticipated: biddingCompanies.isPreQuoteParticipated,
- isBiddingParticipated: biddingCompanies.isBiddingParticipated,
hasSpecificationMeeting: biddings.hasSpecificationMeeting,
// 응답한 조건들 (company_condition_responses) - 제시된 조건과 응답 모두 여기서 관리
paymentTermsResponse: companyConditionResponses.paymentTermsResponse,
diff --git a/lib/bidding/detail/table/bidding-detail-content.tsx b/lib/bidding/detail/table/bidding-detail-content.tsx
index a96509a9..895016a2 100644
--- a/lib/bidding/detail/table/bidding-detail-content.tsx
+++ b/lib/bidding/detail/table/bidding-detail-content.tsx
@@ -35,7 +35,7 @@ export function BiddingDetailContent({
award: false
})
- const [refreshTrigger, setRefreshTrigger] = React.useState(0)
+ const [, setRefreshTrigger] = React.useState(0)
// PR 아이템 다이얼로그 관련 state
const [isItemDetailsDialogOpen, setIsItemDetailsDialogOpen] = React.useState(false)
@@ -80,14 +80,10 @@ export function BiddingDetailContent({
bidding={bidding}
vendors={quotationVendors}
onRefresh={handleRefresh}
- onOpenItemsDialog={() => openDialog('items')}
onOpenTargetPriceDialog={() => openDialog('targetPrice')}
onOpenSelectionReasonDialog={() => openDialog('selectionReason')}
- onOpenAwardDialog={() => openDialog('award')}
onViewItemDetails={handleViewItemDetails}
onEdit={undefined}
- onDelete={undefined}
- onSelectWinner={undefined}
/>
<BiddingDetailItemsDialog
diff --git a/lib/bidding/detail/table/bidding-detail-header.tsx b/lib/bidding/detail/table/bidding-detail-header.tsx
deleted file mode 100644
index 8d18472f..00000000
--- a/lib/bidding/detail/table/bidding-detail-header.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import { useRouter } from 'next/navigation'
-import { Bidding, biddingStatusLabels, contractTypeLabels, biddingTypeLabels } from '@/db/schema'
-import { Badge } from '@/components/ui/badge'
-import { Button } from '@/components/ui/button'
-import {
- ArrowLeft,
- Send,
- RotateCcw,
- XCircle,
- Calendar,
- Building2,
- User,
- Package,
- DollarSign,
- Hash
-} from 'lucide-react'
-
-import { formatDate } from '@/lib/utils'
-import {
- registerBidding,
- markAsDisposal,
- createRebidding
-} from '@/lib/bidding/detail/service'
-import { useToast } from '@/hooks/use-toast'
-import { useTransition } from 'react'
-
-interface BiddingDetailHeaderProps {
- bidding: Bidding
-}
-
-export function BiddingDetailHeader({ bidding }: BiddingDetailHeaderProps) {
- const router = useRouter()
- const { toast } = useToast()
- const [isPending, startTransition] = useTransition()
-
- const handleGoBack = () => {
- router.push('/evcp/bid')
- }
-
- return (
- <div className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
- <div className="px-6 py-4">
- {/* 헤더 메인 영역 */}
- <div className="flex items-center justify-between mb-4">
- <div className="flex items-center gap-4 flex-1 min-w-0">
- {/* 제목과 배지 */}
- <div className="flex items-center gap-3 flex-1 min-w-0">
- <h1 className="text-xl font-semibold truncate">{bidding.title}</h1>
- <div className="flex items-center gap-2 flex-shrink-0">
- <Badge variant="outline" className="font-mono text-xs">
- <Hash className="w-3 h-3 mr-1" />
- {bidding.biddingNumber}
- {bidding.revision && bidding.revision > 0 && ` Rev.${bidding.revision}`}
- </Badge>
- <Badge variant={
- bidding.status === 'bidding_disposal' ? 'destructive' :
- bidding.status === 'vendor_selected' ? 'default' :
- 'secondary'
- } className="text-xs">
- {biddingStatusLabels[bidding.status]}
- </Badge>
- </div>
- </div>
- </div>
- </div>
-
- {/* 세부 정보 영역 */}
-
- {/* 일정 정보 */}
- {/* {(bidding.submissionStartDate || bidding.evaluationDate || bidding.preQuoteDate || bidding.biddingRegistrationDate) && (
- <div className="flex flex-wrap items-center gap-4 mt-3 pt-3 border-t border-border/50">
- <Calendar className="w-4 h-4 text-muted-foreground flex-shrink-0" />
- <div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground">
- {bidding.submissionStartDate && bidding.submissionEndDate && (
- <div>
- <span className="font-medium">제출기간:</span> {formatDate(bidding.submissionStartDate, 'KR')} ~ {formatDate(bidding.submissionEndDate, 'KR')}
- </div>
- )}
- {bidding.evaluationDate && (
- <div>
- <span className="font-medium">평가일:</span> {formatDate(bidding.evaluationDate, 'KR')}
- </div>
- )}
- {bidding.preQuoteDate && (
- <div>
- <span className="font-medium">사전견적일:</span> {formatDate(bidding.preQuoteDate, 'KR')}
- </div>
- )}
- {bidding.biddingRegistrationDate && (
- <div>
- <span className="font-medium">입찰등록일:</span> {formatDate(bidding.biddingRegistrationDate, 'KR')}
- </div>
- )}
- </div>
- </div>
- )} */}
- </div>
- </div>
- )
-} \ No newline at end of file
diff --git a/lib/bidding/detail/table/bidding-detail-items-dialog.tsx b/lib/bidding/detail/table/bidding-detail-items-dialog.tsx
index 2bab3ef0..8c2ae44a 100644
--- a/lib/bidding/detail/table/bidding-detail-items-dialog.tsx
+++ b/lib/bidding/detail/table/bidding-detail-items-dialog.tsx
@@ -17,7 +17,6 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table'
-import { Badge } from '@/components/ui/badge'
import { formatDate } from '@/lib/utils'
interface PrItem {
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx b/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx
index 3b42cc88..782c5f7a 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx
@@ -6,7 +6,7 @@ import { Checkbox } from "@/components/ui/checkbox"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
- MoreHorizontal, Edit, Trash2, Trophy
+ MoreHorizontal
} from "lucide-react"
import {
DropdownMenu,
@@ -20,8 +20,6 @@ import { QuotationVendor } from "@/lib/bidding/detail/service"
interface GetVendorColumnsProps {
onEdit: (vendor: QuotationVendor) => void
- onDelete: (vendor: QuotationVendor) => void
- onSelectWinner: (vendor: QuotationVendor) => void
onViewPriceAdjustment?: (vendor: QuotationVendor) => void
onViewItemDetails?: (vendor: QuotationVendor) => void
onSendBidding?: (vendor: QuotationVendor) => void
@@ -30,9 +28,6 @@ interface GetVendorColumnsProps {
export function getBiddingDetailVendorColumns({
onEdit,
- onDelete,
- onSelectWinner,
- onViewPriceAdjustment,
onViewItemDetails,
onSendBidding,
onUpdateParticipation
@@ -182,17 +177,6 @@ export function getBiddingDetailVendorColumns({
<span className="text-xs text-muted-foreground ml-2">(입찰참여 필요)</span>
)}
</DropdownMenuItem>
- {vendor.status !== 'selected' && (
- <DropdownMenuItem
- onClick={() => onSelectWinner(vendor)}
- disabled={vendor.isBiddingParticipated !== true}
- >
- 낙찰 선정
- {vendor.isBiddingParticipated !== true && (
- <span className="text-xs text-muted-foreground ml-2">(입찰참여 필요)</span>
- )}
- </DropdownMenuItem>
- )}
{/* 입찰 참여여부 관리 */}
{vendor.isBiddingParticipated === null && onUpdateParticipation && (
@@ -217,13 +201,6 @@ export function getBiddingDetailVendorColumns({
</>
)}
- <DropdownMenuSeparator />
- <DropdownMenuItem
- onClick={() => onDelete(vendor)}
- className="text-destructive"
- >
- 삭제
- </DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx b/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx
index e029d536..f43e850a 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx
@@ -4,8 +4,6 @@ import * as React from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
-import { Textarea } from '@/components/ui/textarea'
-import { Checkbox } from '@/components/ui/checkbox'
import {
Dialog,
DialogContent,
@@ -14,13 +12,6 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select'
import { updateBiddingDetailVendor } from '@/lib/bidding/detail/service'
import { QuotationVendor } from '@/lib/bidding/detail/service'
import { useToast } from '@/hooks/use-toast'
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-table.tsx b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
index a9778636..3e8adda9 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
@@ -7,33 +7,22 @@ import { useDataTable } from '@/hooks/use-data-table'
import { DataTable } from '@/components/data-table/data-table'
import { DataTableAdvancedToolbar } from '@/components/data-table/data-table-advanced-toolbar'
import { BiddingDetailVendorToolbarActions } from './bidding-detail-vendor-toolbar-actions'
-import { BiddingDetailVendorCreateDialog } from './bidding-detail-vendor-create-dialog'
import { BiddingDetailVendorEditDialog } from './bidding-detail-vendor-edit-dialog'
import { BiddingAwardDialog } from './bidding-award-dialog'
import { getBiddingDetailVendorColumns } from './bidding-detail-vendor-columns'
import { QuotationVendor, getPriceAdjustmentFormByBiddingCompanyId } from '@/lib/bidding/detail/service'
import { Bidding } from '@/db/schema'
import { PriceAdjustmentDialog } from '@/components/bidding/price-adjustment-dialog'
-import {
- deleteQuotationVendor,
- selectWinner
-} from '@/lib/bidding/detail/service'
-import { selectWinnerSchema } from '@/lib/bidding/validation'
import { useToast } from '@/hooks/use-toast'
-import { useTransition, useCallback } from 'react'
interface BiddingDetailVendorTableContentProps {
biddingId: number
bidding: Bidding
vendors: QuotationVendor[]
onRefresh: () => void
- onOpenItemsDialog: () => void
onOpenTargetPriceDialog: () => void
onOpenSelectionReasonDialog: () => void
- onOpenAwardDialog: () => void
onEdit?: (vendor: QuotationVendor) => void
- onDelete?: (vendor: QuotationVendor) => void
- onSelectWinner?: (vendor: QuotationVendor) => void
onViewItemDetails?: (vendor: QuotationVendor) => void
}
@@ -93,18 +82,12 @@ export function BiddingDetailVendorTableContent({
bidding,
vendors,
onRefresh,
- onOpenItemsDialog,
onOpenTargetPriceDialog,
- onOpenSelectionReasonDialog,
- onOpenAwardDialog,
onEdit,
- onDelete,
- onSelectWinner,
onViewItemDetails
}: BiddingDetailVendorTableContentProps) {
const { data: session } = useSession()
const { toast } = useToast()
- const [isPending, startTransition] = useTransition()
// 세션에서 사용자 ID 가져오기
const userId = session?.user?.id || ''
@@ -114,52 +97,6 @@ export function BiddingDetailVendorTableContent({
const [priceAdjustmentData, setPriceAdjustmentData] = React.useState<any>(null)
const [isPriceAdjustmentDialogOpen, setIsPriceAdjustmentDialogOpen] = React.useState(false)
- const handleSelectWinner = useCallback((vendor: QuotationVendor) => {
- if (!vendor.awardRatio || vendor.awardRatio <= 0) {
- toast({
- title: '오류',
- description: '발주비율을 먼저 설정해주세요.',
- variant: 'destructive',
- })
- return
- }
-
- if (!confirm(`${vendor.vendorName} 업체를 낙찰자로 선정하시겠습니까?`)) return
-
- startTransition(async () => {
- const result = selectWinnerSchema.safeParse({
- biddingId,
- vendorId: vendor.id,
- awardRatio: vendor.awardRatio || 0,
- })
-
- if (!result.success) {
- toast({
- title: '유효성 오류',
- description: result.error.issues[0]?.message || '입력값을 확인해주세요.',
- variant: 'destructive',
- })
- return
- }
-
- const response = await selectWinner(biddingId, vendor.id, vendor.awardRatio || 0, userId)
-
- if (response.success) {
- toast({
- title: '성공',
- description: response.message,
- })
- onRefresh()
- } else {
- toast({
- title: '오류',
- description: response.error,
- variant: 'destructive',
- })
- }
- })
- }, [toast, startTransition, biddingId, userId, selectWinnerSchema, selectWinner, onRefresh])
-
const handleEdit = (vendor: QuotationVendor) => {
setSelectedVendor(vendor)
setIsEditDialogOpen(true)
@@ -192,12 +129,10 @@ export function BiddingDetailVendorTableContent({
const columns = React.useMemo(
() => getBiddingDetailVendorColumns({
onEdit: onEdit || handleEdit,
- onDelete: onDelete,
- onSelectWinner: onSelectWinner || handleSelectWinner,
onViewPriceAdjustment: handleViewPriceAdjustment,
onViewItemDetails: onViewItemDetails
}),
- [onEdit, onDelete, onSelectWinner, handleEdit, handleSelectWinner, handleViewPriceAdjustment, onViewItemDetails]
+ [onEdit, handleEdit, handleViewPriceAdjustment, onViewItemDetails]
)
const { table } = useDataTable({
@@ -224,11 +159,9 @@ export function BiddingDetailVendorTableContent({
shallow={false}
>
<BiddingDetailVendorToolbarActions
- table={table}
biddingId={biddingId}
bidding={bidding}
userId={userId}
- onOpenItemsDialog={onOpenItemsDialog}
onOpenTargetPriceDialog={onOpenTargetPriceDialog}
onOpenAwardDialog={() => setIsAwardDialogOpen(true)}
onSuccess={onRefresh}
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx
index eec44bb1..5d1bfde7 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx
@@ -1,12 +1,11 @@
"use client"
import * as React from "react"
-import { type Table } from "@tanstack/react-table"
import { useRouter } from "next/navigation"
import { useTransition } from "react"
import { Button } from "@/components/ui/button"
import { Plus, Send, RotateCcw, XCircle, Trophy, FileText, DollarSign } from "lucide-react"
-import { QuotationVendor, registerBidding, markAsDisposal, createRebidding, awardBidding } from "@/lib/bidding/detail/service"
+import { registerBidding, markAsDisposal, createRebidding } from "@/lib/bidding/detail/service"
import { sendBiddingBasicContracts, getSelectedVendorsForBidding } from "@/lib/bidding/pre-quote/service"
import { BiddingDetailVendorCreateDialog } from "./bidding-detail-vendor-create-dialog"
@@ -17,22 +16,18 @@ import { useToast } from "@/hooks/use-toast"
import { BiddingInvitationDialog } from "./bidding-invitation-dialog"
interface BiddingDetailVendorToolbarActionsProps {
- table: Table<QuotationVendor>
biddingId: number
bidding: Bidding
userId: string
- onOpenItemsDialog: () => void
onOpenTargetPriceDialog: () => void
onOpenAwardDialog: () => void
onSuccess: () => void
}
export function BiddingDetailVendorToolbarActions({
- table,
biddingId,
bidding,
userId,
- onOpenItemsDialog,
onOpenTargetPriceDialog,
onOpenAwardDialog,
onSuccess
@@ -306,7 +301,7 @@ export function BiddingDetailVendorToolbarActions({
biddingTitle={bidding.title}
budget={bidding.budget ? parseFloat(bidding.budget.toString()) : null}
targetPrice={bidding.targetPrice ? parseFloat(bidding.targetPrice.toString()) : null}
- currency={bidding.currency}
+ currency={bidding.currency || ''}
/>
<BiddingInvitationDialog
@@ -315,7 +310,7 @@ export function BiddingDetailVendorToolbarActions({
vendors={selectedVendors}
biddingId={biddingId}
biddingTitle={bidding.title || ''}
- projectName={bidding.projectName}
+ projectName={bidding.projectName || ''}
onSend={handleBiddingInvitationSend}
/>
</>
diff --git a/lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx b/lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx
index fb54eaba..c12ac1df 100644
--- a/lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx
+++ b/lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx
@@ -28,7 +28,6 @@ import {
TrendingUp
} from 'lucide-react'
import { useToast } from '@/hooks/use-toast'
-import { useTransition } from 'react'
import { getVendorPricesForBidding } from '../service'
interface VendorPrice {
@@ -70,7 +69,6 @@ export function BiddingVendorPricesDialog({
currency = 'KRW'
}: BiddingVendorPricesDialogProps) {
const { toast } = useToast()
- const [isPending, startTransition] = useTransition()
const [vendorPrices, setVendorPrices] = React.useState<VendorPrice[]>([])
const [isLoading, setIsLoading] = React.useState(false)
const [viewMode, setViewMode] = React.useState<'quantity' | 'weight'>('quantity')