diff options
Diffstat (limited to 'components/bidding')
4 files changed, 191 insertions, 86 deletions
diff --git a/components/bidding/bidding-round-actions.tsx b/components/bidding/bidding-round-actions.tsx index b2db0dfb..86fea72a 100644 --- a/components/bidding/bidding-round-actions.tsx +++ b/components/bidding/bidding-round-actions.tsx @@ -35,16 +35,27 @@ export function BiddingRoundActions({ biddingId, biddingStatus }: BiddingRoundAc const { data: session } = useSession()
const userId = session?.user?.id
- // 차수증가는 유찰 상태에서만 가능
- const canIncreaseRound = biddingStatus === 'bidding_disposal'
-
- // 재입찰도 유찰 상태에서만 가능
+ // 차수증가는 입찰공고 또는 입찰 진행중 상태에서 가능
+ const canIncreaseRound = biddingStatus === 'bidding_generated' || biddingStatus === 'bidding_opened'
+
+ // 유찰 및 낙찰은 입찰 진행중 상태에서 가능 (이 컴포넌트에서는 사용하지 않음)
+
+ // 재입찰은 유찰 상태에서만 가능
const canRebid = biddingStatus === 'bidding_disposal'
const handleRoundIncrease = () => {
+ if (!userId) {
+ toast({
+ title: "오류",
+ description: "사용자 정보를 찾을 수 없습니다.",
+ variant: "destructive",
+ })
+ return
+ }
+ const userIdStr = userId as string
startTransition(async () => {
try {
- const result = await increaseRoundOrRebid(biddingId, userId, 'round_increase')
+ const result = await increaseRoundOrRebid(biddingId, userIdStr, 'round_increase')
if (result.success) {
toast({
@@ -77,9 +88,18 @@ export function BiddingRoundActions({ biddingId, biddingStatus }: BiddingRoundAc }
const handleRebid = () => {
+ if (!userId) {
+ toast({
+ title: "오류",
+ description: "사용자 정보를 찾을 수 없습니다.",
+ variant: "destructive",
+ })
+ return
+ }
+ const userIdStr = userId as string
startTransition(async () => {
try {
- const result = await increaseRoundOrRebid(biddingId, userId, 'rebidding')
+ const result = await increaseRoundOrRebid(biddingId, userIdStr, 'rebidding')
if (result.success) {
toast({
diff --git a/components/bidding/manage/bidding-basic-info-editor.tsx b/components/bidding/manage/bidding-basic-info-editor.tsx index d60c5d88..a956d73c 100644 --- a/components/bidding/manage/bidding-basic-info-editor.tsx +++ b/components/bidding/manage/bidding-basic-info-editor.tsx @@ -26,8 +26,9 @@ import { Textarea } from '@/components/ui/textarea' import { Switch } from '@/components/ui/switch' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' // CreateBiddingInput 타입 정의가 없으므로 CreateBiddingSchema를 확장하여 사용합니다. -import { getBiddingById, updateBiddingBasicInfo, getBiddingConditions, getBiddingNotice, updateBiddingConditions } from '@/lib/bidding/service' -import { getBiddingNoticeTemplate } from '@/lib/bidding/service' +import { getBiddingById, updateBiddingBasicInfo, getBiddingConditions, getBiddingNotice, updateBiddingConditions, getBiddingNoticeTemplate } from '@/lib/bidding/service' +import { getPurchaseGroupCodes } from '@/components/common/selectors/purchase-group-code' +import { getProcurementManagers } from '@/components/common/selectors/procurement-manager' import { getIncotermsForSelection, getPaymentTermsForSelection, @@ -39,8 +40,8 @@ import { contractTypeLabels, biddingTypeLabels, awardCountLabels, biddingNoticeT import TiptapEditor from '@/components/qna/tiptap-editor' import { PurchaseGroupCodeSelector } from '@/components/common/selectors/purchase-group-code' import { ProcurementManagerSelector } from '@/components/common/selectors/procurement-manager' -import type { PurchaseGroupCodeWithUser } from '@/components/common/selectors/purchase-group-code/purchase-group-code-service' -import type { ProcurementManagerWithUser } from '@/components/common/selectors/procurement-manager/procurement-manager-service' +import type { PurchaseGroupCodeWithUser } from '@/components/common/selectors/purchase-group-code' +import type { ProcurementManagerWithUser } from '@/components/common/selectors/procurement-manager' import { getBiddingDocuments, uploadBiddingDocument, deleteBiddingDocument } from '@/lib/bidding/detail/service' import { downloadFile } from '@/lib/file-download' @@ -259,7 +260,7 @@ export function BiddingBasicInfoEditor({ biddingId }: BiddingBasicInfoEditorProp } // Procurement 데이터 로드 - const [paymentTermsData, incotermsData, shippingData, destinationData] = await Promise.all([ + const [paymentTermsData, incotermsData, shippingData, destinationData, purchaseGroupCodes, procurementManagers] = await Promise.all([ getPaymentTermsForSelection().catch(() => []), getIncotermsForSelection().catch(() => []), getPlaceOfShippingForSelection().catch(() => []), @@ -269,6 +270,34 @@ export function BiddingBasicInfoEditor({ biddingId }: BiddingBasicInfoEditorProp setIncotermsOptions(incotermsData) setShippingPlaces(shippingData) setDestinationPlaces(destinationData) + setSelectedBidPic({ + DISPLAY_NAME: bidding.bidPicName || '', + PURCHASE_GROUP_CODE: bidding.bidPicCode || '', + user: { + id: bidding.bidPicUserId || undefined, + } + }) + setSelectedSupplyPic({ + DISPLAY_NAME: bidding.supplyPicName || '', + PROCUREMENT_MANAGER_CODE: bidding.supplyPicCode || '', + user: { + id: bidding.supplyPicUserId || undefined, + } + }) + // // 입찰담당자 및 조달담당자 초기 선택값 설정 + // if (bidding.bidPicCode && purchaseGroupCodes.length > 0) { + // const selectedBidPicData = purchaseGroupCodes.find(code => code.PURCHASE_GROUP_CODE === bidding.bidPicCode) + // if (selectedBidPicData) { + + // } + // } + + // if (bidding.supplyPicCode && procurementManagers.length > 0) { + // const selectedSupplyPicData = procurementManagers.find(manager => manager.PROCUREMENT_MANAGER_CODE === bidding.supplyPicCode) + // if (selectedSupplyPicData) { + + // } + // } // 공고 템플릿 로드 await loadNoticeTemplate(biddingExtended.noticeType || undefined) diff --git a/components/bidding/manage/bidding-items-editor.tsx b/components/bidding/manage/bidding-items-editor.tsx index 96a8d2ae..dc0aaeec 100644 --- a/components/bidding/manage/bidding-items-editor.tsx +++ b/components/bidding/manage/bidding-items-editor.tsx @@ -80,6 +80,7 @@ interface BiddingItemsEditorProps { import { removeBiddingItem, addPRItemForBidding, getBiddingById, getBiddingConditions } from '@/lib/bidding/service' import { CreatePreQuoteRfqDialog } from './create-pre-quote-rfq-dialog' +import { ProcurementItemSelectorDialogSingle } from '@/components/common/selectors/procurement-item/procurement-item-selector-dialog-single' import { Textarea } from '@/components/ui/textarea' import { Label } from '@/components/ui/label' @@ -100,6 +101,7 @@ export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { const [preQuoteDialogOpen, setPreQuoteDialogOpen] = React.useState(false) const [targetPriceCalculationCriteria, setTargetPriceCalculationCriteria] = React.useState('') const [biddingPicUserId, setBiddingPicUserId] = React.useState<number | null>(null) + const [biddingType, setBiddingType] = React.useState<string | null>(null) const [biddingConditions, setBiddingConditions] = React.useState<{ paymentTerms?: string | null taxConditions?: string | null @@ -159,13 +161,15 @@ export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { actualAmount: item.actualAmount ? item.actualAmount.toString() : null, actualCurrency: item.actualCurrency || 'KRW', })) - + // 첫 번째 아이템을 대표로 설정 - formattedItems[0].isRepresentative = true - + if (formattedItems.length > 0) { + formattedItems[0].isRepresentative = true + } + setItems(formattedItems) setDeletedItemIds(new Set()) // 삭제 목록 초기화 - + // 기존 품목 로드 성공 알림 (조용히 표시, 선택적) console.log(`기존 품목 ${formattedItems.length}개를 불러왔습니다.`) } else { @@ -199,7 +203,9 @@ export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { ]) if (bidding) { + console.log('📋 bidding:', bidding.biddingType) setBiddingPicUserId(bidding.bidPicId || null) + setBiddingType(bidding.biddingType || null) setTargetPriceCalculationCriteria(bidding.targetPriceCalculationCriteria || '') } @@ -332,52 +338,73 @@ export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { toast.success('품목 정보가 성공적으로 저장되었습니다.') // 삭제 목록 초기화 setDeletedItemIds(new Set()) + // 데이터 다시 로딩하여 최신 상태 반영 + console.log('🔄 저장 후 데이터 재로드 시작 - biddingId:', biddingId) const prItems = await getPRItemsForBidding(biddingId) - const formattedItems: PRItemInfo[] = prItems.map((item) => ({ - id: item.id, - prNumber: item.prNumber || null, - projectId: item.projectId || null, - projectInfo: item.projectInfo || null, - shi: item.shi || null, - quantity: item.quantity ? item.quantity.toString() : null, - quantityUnit: item.quantityUnit || null, - totalWeight: item.totalWeight ? item.totalWeight.toString() : null, - weightUnit: item.weightUnit || null, - materialDescription: item.itemInfo || null, - hasSpecDocument: item.hasSpecDocument || false, - requestedDeliveryDate: item.requestedDeliveryDate ? new Date(item.requestedDeliveryDate).toISOString().split('T')[0] : null, - isRepresentative: false, - annualUnitPrice: item.annualUnitPrice ? item.annualUnitPrice.toString() : null, - currency: item.currency || 'KRW', - materialGroupNumber: item.materialGroupNumber || null, - materialGroupInfo: item.materialGroupInfo || null, - materialNumber: item.materialNumber || null, - materialInfo: item.materialInfo || null, - priceUnit: item.priceUnit || null, - purchaseUnit: item.purchaseUnit || null, - materialWeight: item.materialWeight ? item.materialWeight.toString() : null, - wbsCode: item.wbsCode || null, - wbsName: item.wbsName || null, - costCenterCode: item.costCenterCode || null, - costCenterName: item.costCenterName || null, - glAccountCode: item.glAccountCode || null, - glAccountName: item.glAccountName || null, - targetUnitPrice: item.targetUnitPrice ? item.targetUnitPrice.toString() : null, - targetAmount: item.targetAmount ? item.targetAmount.toString() : null, - targetCurrency: item.targetCurrency || 'KRW', - budgetAmount: item.budgetAmount ? item.budgetAmount.toString() : null, - budgetCurrency: item.budgetCurrency || 'KRW', - actualAmount: item.actualAmount ? item.actualAmount.toString() : null, - actualCurrency: item.actualCurrency || 'KRW', - })) - - // 첫 번째 아이템을 대표로 설정 - if (formattedItems.length > 0) { - formattedItems[0].isRepresentative = true + console.log('📦 getPRItemsForBidding 결과:', prItems) + + if (prItems && prItems.length > 0) { + console.log('✅ 저장된 아이템 수:', prItems.length) + const formattedItems: PRItemInfo[] = prItems.map((item, index) => { + console.log(`🔍 아이템 ${index + 1}:`, { + id: item.id, + materialGroupNumber: item.materialGroupNumber, + materialNumber: item.materialNumber, + quantity: item.quantity + }) + return { + id: item.id, + prNumber: item.prNumber || null, + projectId: item.projectId || null, + projectInfo: item.projectInfo || null, + shi: item.shi || null, + quantity: item.quantity ? item.quantity.toString() : null, + quantityUnit: item.quantityUnit || null, + totalWeight: item.totalWeight ? item.totalWeight.toString() : null, + weightUnit: item.weightUnit || null, + materialDescription: item.itemInfo || null, + hasSpecDocument: item.hasSpecDocument || false, + requestedDeliveryDate: item.requestedDeliveryDate ? new Date(item.requestedDeliveryDate).toISOString().split('T')[0] : null, + isRepresentative: false, + annualUnitPrice: item.annualUnitPrice ? item.annualUnitPrice.toString() : null, + currency: item.currency || 'KRW', + materialGroupNumber: item.materialGroupNumber || null, + materialGroupInfo: item.materialGroupInfo || null, + materialNumber: item.materialNumber || null, + materialInfo: item.materialInfo || null, + priceUnit: item.priceUnit || null, + purchaseUnit: item.purchaseUnit || null, + materialWeight: item.materialWeight ? item.materialWeight.toString() : null, + wbsCode: item.wbsCode || null, + wbsName: item.wbsName || null, + costCenterCode: item.costCenterCode || null, + costCenterName: item.costCenterName || null, + glAccountCode: item.glAccountCode || null, + glAccountName: item.glAccountName || null, + targetUnitPrice: item.targetUnitPrice ? item.targetUnitPrice.toString() : null, + targetAmount: item.targetAmount ? item.targetAmount.toString() : null, + targetCurrency: item.targetCurrency || 'KRW', + budgetAmount: item.budgetAmount ? item.budgetAmount.toString() : null, + budgetCurrency: item.budgetCurrency || 'KRW', + actualAmount: item.actualAmount ? item.actualAmount.toString() : null, + actualCurrency: item.actualCurrency || 'KRW', + } + }) + + // 첫 번째 아이템을 대표로 설정 + if (formattedItems.length > 0) { + formattedItems[0].isRepresentative = true + } + + console.log('📋 최종 formattedItems:', formattedItems) + setItems(formattedItems) + console.log('✅ 상태 업데이트 완료') + } else { + console.log('❌ 저장 후 데이터가 없음 - 빈 배열 설정') + // 저장 후 데이터가 없으면 빈 배열로 설정 + setItems([]) } - - setItems(formattedItems) } } catch (error) { console.error('Failed to save items:', error) @@ -593,30 +620,57 @@ export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { /> </td> <td className="border-r px-3 py-2"> - <MaterialGroupSelectorDialogSingle - triggerLabel={item.materialGroupNumber || "자재그룹 선택"} - triggerVariant="outline" - selectedMaterial={item.materialGroupNumber ? { - materialGroupCode: item.materialGroupNumber, - materialGroupDescription: item.materialGroupInfo || '', - displayText: `${item.materialGroupNumber} - ${item.materialGroupInfo || ''}` - } : null} - onMaterialSelect={(material) => { - if (material) { - updatePRItem(item.id, { - materialGroupNumber: material.materialGroupCode, - materialGroupInfo: material.materialGroupDescription - }) - } else { - updatePRItem(item.id, { - materialGroupNumber: '', - materialGroupInfo: '' - }) - } - }} - title="자재그룹 선택" - description="자재그룹을 검색하고 선택해주세요." - /> + {biddingType === 'equipment' ? ( + <ProcurementItemSelectorDialogSingle + triggerLabel={item.materialGroupNumber || "품목 선택"} + triggerVariant="outline" + selectedProcurementItem={item.materialGroupNumber ? { + itemCode: item.materialGroupNumber, + itemName: item.materialGroupInfo || '', + displayText: `${item.materialGroupNumber}` + } : null} + onProcurementItemSelect={(procurementItem) => { + if (procurementItem) { + updatePRItem(item.id, { + materialGroupNumber: procurementItem.itemCode, + materialGroupInfo: procurementItem.itemName + }) + } else { + updatePRItem(item.id, { + materialGroupNumber: '', + materialGroupInfo: '' + }) + } + }} + title="품목 선택" + description="품목을 검색하고 선택해주세요." + /> + ) : ( + <MaterialGroupSelectorDialogSingle + triggerLabel={item.materialGroupNumber || "자재그룹 선택"} + triggerVariant="outline" + selectedMaterial={item.materialGroupNumber ? { + materialGroupCode: item.materialGroupNumber, + materialGroupDescription: item.materialGroupInfo || '', + displayText: `${item.materialGroupNumber}` + } : null} + onMaterialSelect={(material) => { + if (material) { + updatePRItem(item.id, { + materialGroupNumber: material.materialGroupCode, + materialGroupInfo: material.materialGroupDescription + }) + } else { + updatePRItem(item.id, { + materialGroupNumber: '', + materialGroupInfo: '' + }) + } + }} + title="자재그룹 선택" + description="자재그룹을 검색하고 선택해주세요." + /> + )} </td> <td className="border-r px-3 py-2"> <Input @@ -633,7 +687,7 @@ export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { selectedMaterial={item.materialNumber ? { materialCode: item.materialNumber, materialName: item.materialInfo || '', - displayText: `${item.materialNumber} - ${item.materialInfo || ''}` + displayText: `${item.materialNumber}` } : null} onMaterialSelect={(material) => { if (material) { @@ -830,7 +884,7 @@ export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { > {item.wbsCode ? ( <span className="truncate"> - {`${item.wbsCode}${item.wbsName ? ` - ${item.wbsName}` : ''}`} + {`${item.wbsCode}`} </span> ) : ( <span className="text-muted-foreground">WBS 코드 선택</span> @@ -880,7 +934,7 @@ export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { > {item.costCenterCode ? ( <span className="truncate"> - {`${item.costCenterCode}${item.costCenterName ? ` - ${item.costCenterName}` : ''}`} + {`${item.costCenterCode}`} </span> ) : ( <span className="text-muted-foreground">코스트센터 선택</span> @@ -931,7 +985,7 @@ export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { > {item.glAccountCode ? ( <span className="truncate"> - {`${item.glAccountCode}${item.glAccountName ? ` - ${item.glAccountName}` : ''}`} + {`${item.glAccountCode}`} </span> ) : ( <span className="text-muted-foreground">GL계정 선택</span> diff --git a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx index 88732deb..c49f6232 100644 --- a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx +++ b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx @@ -95,6 +95,7 @@ interface CreatePreQuoteRfqDialogProps { totalWeight?: string | null
weightUnit?: string | null
}>
+ picUserId?: number | null
biddingConditions?: {
paymentTerms?: string | null
taxConditions?: string | null
@@ -114,6 +115,7 @@ export function CreatePreQuoteRfqDialog({ onOpenChange,
biddingId,
biddingItems,
+ picUserId,
biddingConditions,
onSuccess
}: CreatePreQuoteRfqDialogProps) {
|
