diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-18 05:26:47 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-18 05:26:47 +0000 |
| commit | 6a8d6cda345d13352961d40abd44cfa5daf81591 (patch) | |
| tree | d675bdaddbf4a518e26673f256afb7aa29686b19 /lib | |
| parent | 53fce4bf4ac8310bd02d77e564f28d3c12228bd1 (diff) | |
(최겸) 구매 입찰 수정, PCR 수정
Diffstat (limited to 'lib')
10 files changed, 20 insertions, 436 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') diff --git a/lib/pcr/table/pcr-table-toolbar-actions.tsx b/lib/pcr/table/pcr-table-toolbar-actions.tsx index 3e2394fb..08a0ad72 100644 --- a/lib/pcr/table/pcr-table-toolbar-actions.tsx +++ b/lib/pcr/table/pcr-table-toolbar-actions.tsx @@ -64,7 +64,8 @@ export function PcrTableToolbarActions<TData>({ return (
<div className="flex items-center gap-2">
-
+ {isPartnersPage && (
+ <>
{/* 승인 버튼 */}
<Button
variant="default"
@@ -88,6 +89,8 @@ export function PcrTableToolbarActions<TData>({ <XCircle className="size-4" />
거절
</Button>
+ </>
+ )}
{/* PCR 생성 다이얼로그 - Partners 페이지에서는 표시하지 않음 */}
{!isPartnersPage && (
|
