summaryrefslogtreecommitdiff
path: root/lib/bidding/detail
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-16 09:20:58 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-16 09:20:58 +0000
commit6c11fccc84f4c84fa72ee01f9caad9f76f35cea2 (patch)
treefa88d10ea7d21fe6b59ed0c1569856a73d56547a /lib/bidding/detail
parent14e3990aba7e1ad1cdd0965cbd167c50230cbfbf (diff)
(대표님, 최겸) 계약, 업로드 관련, 메뉴처리, 입찰, 프리쿼트, rfqLast관련, tbeLast관련
Diffstat (limited to 'lib/bidding/detail')
-rw-r--r--lib/bidding/detail/service.ts14
-rw-r--r--lib/bidding/detail/table/bidding-detail-vendor-create-dialog.tsx4
-rw-r--r--lib/bidding/detail/table/bidding-detail-vendor-table.tsx32
-rw-r--r--lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx68
4 files changed, 55 insertions, 63 deletions
diff --git a/lib/bidding/detail/service.ts b/lib/bidding/detail/service.ts
index df8427da..b00a4f4f 100644
--- a/lib/bidding/detail/service.ts
+++ b/lib/bidding/detail/service.ts
@@ -632,7 +632,7 @@ export async function createBiddingDetailVendor(
companyId: vendorId,
invitationStatus: 'pending',
isPreQuoteSelected: true, // 본입찰 등록 기본값
- isWinner: false,
+ isWinner: null, // 미정 상태로 초기화 0916
createdAt: new Date(),
updatedAt: new Date(),
}).returning({ id: biddingCompanies.id })
@@ -679,13 +679,23 @@ 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: false,
+ isWinner: null, // 미정 상태로 초기화
contactPerson: input.contactPerson,
contactEmail: input.contactEmail,
contactPhone: input.contactPhone,
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-create-dialog.tsx b/lib/bidding/detail/table/bidding-detail-vendor-create-dialog.tsx
index 5e85af06..f35957bc 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-create-dialog.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-create-dialog.tsx
@@ -36,7 +36,7 @@ import {
import { Check, ChevronsUpDown, Search } from 'lucide-react'
import { cn } from '@/lib/utils'
import { createBiddingDetailVendor } from '@/lib/bidding/detail/service'
-import { searchVendors } from '@/lib/vendors/service'
+import { searchVendorsForBidding } from '@/lib/bidding/service'
import { useToast } from '@/hooks/use-toast'
import { useTransition } from 'react'
@@ -83,7 +83,7 @@ export function BiddingDetailVendorCreateDialog({
}
try {
- const result = await searchVendors(vendorSearchValue.trim(), 10)
+ const result = await searchVendorsForBidding(vendorSearchValue.trim(), biddingId, 10)
setVendors(result)
} catch (error) {
console.error('Vendor search failed:', error)
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-table.tsx b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
index 95f63ce9..a9778636 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
@@ -20,7 +20,7 @@ import {
} from '@/lib/bidding/detail/service'
import { selectWinnerSchema } from '@/lib/bidding/validation'
import { useToast } from '@/hooks/use-toast'
-import { useTransition } from 'react'
+import { useTransition, useCallback } from 'react'
interface BiddingDetailVendorTableContentProps {
biddingId: number
@@ -114,29 +114,7 @@ export function BiddingDetailVendorTableContent({
const [priceAdjustmentData, setPriceAdjustmentData] = React.useState<any>(null)
const [isPriceAdjustmentDialogOpen, setIsPriceAdjustmentDialogOpen] = React.useState(false)
- const handleDelete = (vendor: QuotationVendor) => {
- if (!confirm(`${vendor.vendorName} 업체를 삭제하시겠습니까?`)) return
-
- startTransition(async () => {
- const response = await deleteQuotationVendor(vendor.id)
-
- if (response.success) {
- toast({
- title: '성공',
- description: response.message,
- })
- onRefresh()
- } else {
- toast({
- title: '오류',
- description: response.error,
- variant: 'destructive',
- })
- }
- })
- }
-
- const handleSelectWinner = (vendor: QuotationVendor) => {
+ const handleSelectWinner = useCallback((vendor: QuotationVendor) => {
if (!vendor.awardRatio || vendor.awardRatio <= 0) {
toast({
title: '오류',
@@ -180,7 +158,7 @@ export function BiddingDetailVendorTableContent({
})
}
})
- }
+ }, [toast, startTransition, biddingId, userId, selectWinnerSchema, selectWinner, onRefresh])
const handleEdit = (vendor: QuotationVendor) => {
setSelectedVendor(vendor)
@@ -214,12 +192,12 @@ export function BiddingDetailVendorTableContent({
const columns = React.useMemo(
() => getBiddingDetailVendorColumns({
onEdit: onEdit || handleEdit,
- onDelete: onDelete || handleDelete,
+ onDelete: onDelete,
onSelectWinner: onSelectWinner || handleSelectWinner,
onViewPriceAdjustment: handleViewPriceAdjustment,
onViewItemDetails: onViewItemDetails
}),
- [onEdit, onDelete, onSelectWinner, handleEdit, handleDelete, handleSelectWinner, handleViewPriceAdjustment, onViewItemDetails]
+ [onEdit, onDelete, onSelectWinner, handleEdit, handleSelectWinner, handleViewPriceAdjustment, onViewItemDetails]
)
const { table } = useDataTable({
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 0b707944..893fb185 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx
@@ -180,6 +180,8 @@ export function BiddingDetailVendorToolbarActions({
<>
<div className="flex items-center gap-2">
{/* 상태별 액션 버튼 */}
+ {bidding.status !== 'bidding_closed' && bidding.status !== 'vendor_selected' && (
+ <>
<Button
variant="default"
size="sm"
@@ -223,7 +225,6 @@ export function BiddingDetailVendorToolbarActions({
{/* 구분선 */}
{(bidding.status === 'bidding_generated' ||
- bidding.status === 'bidding_closed' ||
bidding.status === 'bidding_disposal') && (
<div className="h-4 w-px bg-border mx-1" />
)}
@@ -236,37 +237,40 @@ export function BiddingDetailVendorToolbarActions({
>
품목 정보
</Button> */}
- <Button
- variant="outline"
- size="sm"
- onClick={onOpenTargetPriceDialog}
- >
- 내정가 산정
- </Button>
- <Button
- variant="outline"
- size="sm"
- onClick={handleCreateVendor}
- >
- <Plus className="mr-2 h-4 w-4" />
- 업체 추가
- </Button>
- <Button
- variant="outline"
- size="sm"
- onClick={handleDocumentUpload}
- >
- <FileText className="mr-2 h-4 w-4" />
- 입찰문서 등록
- </Button>
- <Button
- variant="outline"
- size="sm"
- onClick={handleViewVendorPrices}
- >
- <DollarSign className="mr-2 h-4 w-4" />
- 입찰가 비교
- </Button>
+
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={onOpenTargetPriceDialog}
+ >
+ 내정가 산정
+ </Button>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleCreateVendor}
+ >
+ <Plus className="mr-2 h-4 w-4" />
+ 업체 추가
+ </Button>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleDocumentUpload}
+ >
+ <FileText className="mr-2 h-4 w-4" />
+ 입찰문서 등록
+ </Button>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleViewVendorPrices}
+ >
+ <DollarSign className="mr-2 h-4 w-4" />
+ 입찰가 비교
+ </Button>
+ </>
+ )}
</div>
<BiddingDetailVendorCreateDialog