From 25749225689c3934bc10ad1e8285e13020b61282 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 4 Dec 2025 09:04:09 +0000
Subject: (최겸)구매 입찰, 계약 수정
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../bidding/manage/create-pre-quote-rfq-dialog.tsx | 38 +++++++++++++++++++---
 1 file changed, 33 insertions(+), 5 deletions(-)

(limited to 'components/bidding/manage/create-pre-quote-rfq-dialog.tsx')

diff --git a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx
index de3c19ff..1ab7a40f 100644
--- a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx
+++ b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx
@@ -465,7 +465,7 @@ export function CreatePreQuoteRfqDialog({
                                 )}
                               >
                                 {field.value ? (
-                                  format(field.value, "yyyy-MM-dd")
+                                  format(field.value, "yyyy-MM-dd HH:mm")
                                 ) : (
                                   <span>제출마감일을 선택하세요 (선택)</span>
                                 )}
@@ -477,12 +477,40 @@ export function CreatePreQuoteRfqDialog({
                             <Calendar
                               mode="single"
                               selected={field.value}
-                              onSelect={field.onChange}
-                              disabled={(date) =>
-                                date < new Date() || date < new Date("1900-01-01")
-                              }
+                              onSelect={(date) => {
+                                if (!date) {
+                                  field.onChange(undefined)
+                                  return
+                                }
+                                const newDate = new Date(date)
+                                if (field.value) {
+                                  newDate.setHours(field.value.getHours(), field.value.getMinutes())
+                                } else {
+                                  newDate.setHours(0, 0, 0, 0)
+                                }
+                                field.onChange(newDate)
+                              }}
+                              disabled={(date) => {
+                                const today = new Date()
+                                today.setHours(0, 0, 0, 0)
+                                return date < today || date < new Date("1900-01-01")
+                              }}
                               initialFocus
                             />
+                            <div className="p-3 border-t border-border">
+                              <Input
+                                type="time"
+                                value={field.value ? format(field.value, "HH:mm") : ""}
+                                onChange={(e) => {
+                                  if (field.value) {
+                                    const [hours, minutes] = e.target.value.split(':').map(Number)
+                                    const newDate = new Date(field.value)
+                                    newDate.setHours(hours, minutes)
+                                    field.onChange(newDate)
+                                  }
+                                }}
+                              />
+                            </div>
                           </PopoverContent>
                         </Popover>
                         <FormMessage />
-- 
cgit v1.2.3


From 93b6b8868d409c7f6c9d9222b93750848caaedde Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 5 Dec 2025 03:28:04 +0000
Subject: (최겸) 구매 입찰 수정
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../bidding/create/bidding-create-dialog.tsx       | 51 ++++++++----
 .../bidding/manage/bidding-basic-info-editor.tsx   |  2 -
 .../bidding/manage/bidding-companies-editor.tsx    | 17 +++-
 .../manage/bidding-detail-vendor-create-dialog.tsx | 15 +++-
 .../bidding/manage/create-pre-quote-rfq-dialog.tsx | 90 ++++++++--------------
 lib/bidding/actions.ts                             |  2 +-
 lib/bidding/approval-actions.ts                    | 16 ++--
 lib/bidding/detail/service.ts                      | 14 +++-
 lib/bidding/handlers.ts                            | 21 ++++-
 .../manage/import-bidding-items-from-excel.ts      |  4 +-
 lib/bidding/pre-quote/service.ts                   | 10 ++-
 lib/bidding/service.ts                             | 61 ++++++++++++---
 lib/bidding/validation.ts                          |  2 -
 .../vendor/partners-bidding-attendance-dialog.tsx  |  1 -
 lib/bidding/vendor/partners-bidding-detail.tsx     |  7 +-
 lib/bidding/vendor/partners-bidding-list.tsx       |  1 -
 16 files changed, 196 insertions(+), 118 deletions(-)

(limited to 'components/bidding/manage/create-pre-quote-rfq-dialog.tsx')

diff --git a/components/bidding/create/bidding-create-dialog.tsx b/components/bidding/create/bidding-create-dialog.tsx
index b3972e11..af33f1f6 100644
--- a/components/bidding/create/bidding-create-dialog.tsx
+++ b/components/bidding/create/bidding-create-dialog.tsx
@@ -63,7 +63,7 @@ import { PurchaseGroupCodeSelector } from '@/components/common/selectors/purchas
 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 { createBidding } from '@/lib/bidding/service'
+import { createBidding, getUserDetails } from '@/lib/bidding/service'
 import { useSession } from 'next-auth/react'
 import { useRouter } from 'next/navigation'
 
@@ -97,13 +97,6 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp
     sparePartOptions: '',
   })
 
-  // 구매요청자 정보 (현재 사용자)
-  // React.useEffect(() => {
-  //   // 실제로는 현재 로그인한 사용자의 정보를 가져와야 함
-  //   // 임시로 기본값 설정
-  //   form.setValue('requesterName', '김두진') // 실제로는 API에서 가져와야 함
-  // }, [form])
-
   const [shiAttachmentFiles, setShiAttachmentFiles] = React.useState<File[]>([])
   const [vendorAttachmentFiles, setVendorAttachmentFiles] = React.useState<File[]>([])
 
@@ -164,13 +157,41 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp
 
   React.useEffect(() => {
     if (isOpen) {
-      if (userId && session?.user?.name) {
-        // 현재 사용자의 정보를 임시로 입찰담당자로 설정
-        form.setValue('bidPicName', session.user.name)
-        form.setValue('bidPicId', userId)
-        // userCode는 현재 세션에 없으므로 이름으로 설정 (실제로는 API에서 가져와야 함)
-        // form.setValue('bidPicCode', session.user.name)
+      const initUser = async () => {
+        if (userId) {
+          try {
+            const user = await getUserDetails(userId)
+            if (user) {
+              // 현재 사용자의 정보를 입찰담당자로 설정
+              form.setValue('bidPicName', user.name)
+              form.setValue('bidPicId', user.id)
+              form.setValue('bidPicCode', user.userCode || '')
+
+              // 담당자 selector 상태 업데이트
+              setSelectedBidPic({
+                PURCHASE_GROUP_CODE: user.userCode || '',
+                DISPLAY_NAME: user.name,
+                EMPLOYEE_NUMBER: user.employeeNumber || '',
+                user: {
+                  id: user.id,
+                  name: user.name,
+                  email: '', 
+                  employeeNumber: user.employeeNumber
+                }
+              } as any)
+            }
+          } catch (error) {
+            console.error('Failed to fetch user details:', error)
+            // 실패 시 세션 정보로 폴백
+            if (session?.user?.name) {
+              form.setValue('bidPicName', session.user.name)
+              form.setValue('bidPicId', userId)
+            }
+          }
+        }
       }
+      initUser()
+
       loadPaymentTerms()
       loadIncoterms()
       loadShippingPlaces()
@@ -181,7 +202,7 @@ export function BiddingCreateDialog({ form, onSuccess }: BiddingCreateDialogProp
         form.setValue('biddingConditions.taxConditions', 'V1')
       }
     }
-  }, [isOpen, loadPaymentTerms, loadIncoterms, loadShippingPlaces, loadDestinationPlaces, form])
+  }, [isOpen, userId, session, form, loadPaymentTerms, loadIncoterms, loadShippingPlaces, loadDestinationPlaces])
 
   // SHI용 파일 첨부 핸들러
   const handleShiFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
diff --git a/components/bidding/manage/bidding-basic-info-editor.tsx b/components/bidding/manage/bidding-basic-info-editor.tsx
index 27a2c097..13c58311 100644
--- a/components/bidding/manage/bidding-basic-info-editor.tsx
+++ b/components/bidding/manage/bidding-basic-info-editor.tsx
@@ -88,7 +88,6 @@ interface BiddingBasicInfo {
   contractEndDate?: string
   submissionStartDate?: string
   submissionEndDate?: string
-  evaluationDate?: string
   hasSpecificationMeeting?: boolean
   hasPrDocument?: boolean
   currency?: string
@@ -252,7 +251,6 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
             contractEndDate: formatDate(bidding.contractEndDate),
             submissionStartDate: formatDateTime(bidding.submissionStartDate),
             submissionEndDate: formatDateTime(bidding.submissionEndDate),
-            evaluationDate: formatDateTime(bidding.evaluationDate),
             hasSpecificationMeeting: bidding.hasSpecificationMeeting || false,
             hasPrDocument: bidding.hasPrDocument || false,
             currency: bidding.currency || 'KRW',
diff --git a/components/bidding/manage/bidding-companies-editor.tsx b/components/bidding/manage/bidding-companies-editor.tsx
index 4c3e6bbc..9bfea90e 100644
--- a/components/bidding/manage/bidding-companies-editor.tsx
+++ b/components/bidding/manage/bidding-companies-editor.tsx
@@ -566,7 +566,22 @@ export function BiddingCompaniesEditor({ biddingId, readonly = false }: BiddingC
                     </TableCell>
                     <TableCell className="font-medium">{vendor.vendorName}</TableCell>
                     <TableCell>{vendor.vendorCode}</TableCell>
-                    <TableCell>{vendor.businessSize || '-'}</TableCell>
+                    <TableCell>
+                      {(() => {
+                        switch (vendor.businessSize) {
+                          case 'A':
+                            return '대기업';
+                          case 'B':
+                            return '중견기업';
+                          case 'C':
+                            return '중소기업';
+                          case 'D':
+                            return '소기업';
+                          default:
+                            return '-';
+                        }
+                      })()}
+                    </TableCell>
                     <TableCell>
                       {vendor.companyId && vendorFirstContacts.has(vendor.companyId)
                         ? vendorFirstContacts.get(vendor.companyId)!.contactName
diff --git a/components/bidding/manage/bidding-detail-vendor-create-dialog.tsx b/components/bidding/manage/bidding-detail-vendor-create-dialog.tsx
index 0dd9f0eb..489f104d 100644
--- a/components/bidding/manage/bidding-detail-vendor-create-dialog.tsx
+++ b/components/bidding/manage/bidding-detail-vendor-create-dialog.tsx
@@ -408,7 +408,20 @@ export function BiddingDetailVendorCreateDialog({
                             연동제 적용요건 문의
                           </Label>
                           <span className="text-xs text-muted-foreground">
-                            기업규모: {businessSizeMap[item.vendor.id] || '미정'}
+                            기업규모: {(() => {
+                              switch (businessSizeMap[item.vendor.id]) {
+                                case 'A':
+                                  return '대기업';
+                                case 'B':
+                                  return '중견기업';
+                                case 'C':
+                                  return '중소기업';
+                                case 'D':
+                                  return '소기업';
+                                default:
+                                  return '-';
+                              }
+                            })()}
                           </span>
                         </div>
                       </div>
diff --git a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx
index 1ab7a40f..b0cecc25 100644
--- a/components/bidding/manage/create-pre-quote-rfq-dialog.tsx
+++ b/components/bidding/manage/create-pre-quote-rfq-dialog.tsx
@@ -26,13 +26,6 @@ import {
   FormMessage,
   FormDescription,
 } from "@/components/ui/form"
-import {
-  Select,
-  SelectContent,
-  SelectItem,
-  SelectTrigger,
-  SelectValue,
-} from "@/components/ui/select"
 import { Input } from "@/components/ui/input"
 import { Textarea } from "@/components/ui/textarea"
 import {
@@ -41,20 +34,15 @@ import {
   PopoverTrigger,
 } from "@/components/ui/popover"
 import { Calendar } from "@/components/ui/calendar"
-import { Badge } from "@/components/ui/badge"
 import { cn } from "@/lib/utils"
 import { toast } from "sonner"
 import { ScrollArea } from "@/components/ui/scroll-area"
 import { Separator } from "@/components/ui/separator"
 import { createPreQuoteRfqAction } from "@/lib/bidding/pre-quote/service"
-import { previewGeneralRfqCode } from "@/lib/rfq-last/service"
-import { MaterialGroupSelectorDialogSingle } from "@/components/common/material/material-group-selector-dialog-single"
-import { MaterialSearchItem } from "@/lib/material/material-group-service"
-import { MaterialSelectorDialogSingle } from "@/components/common/selectors/material/material-selector-dialog-single"
-import { MaterialSearchItem as SAPMaterialSearchItem } from "@/components/common/selectors/material/material-service"
 import { PurchaseGroupCodeSelector } from "@/components/common/selectors/purchase-group-code/purchase-group-code-selector"
 import type { PurchaseGroupCodeWithUser } from "@/components/common/selectors/purchase-group-code"
 import { getBiddingById } from "@/lib/bidding/service"
+import { getProjectIdByCodeAndName } from "@/lib/bidding/manage/project-utils"
 
 // 아이템 스키마
 const itemSchema = z.object({
@@ -64,6 +52,8 @@ const itemSchema = z.object({
   materialName: z.string().optional(),
   quantity: z.number().min(1, "수량은 1 이상이어야 합니다"),
   uom: z.string().min(1, "단위를 입력해주세요"),
+  totalWeight: z.union([z.number(), z.string(), z.null()]).optional(), // 중량 추가
+  weightUnit: z.string().optional().nullable(), // 중량단위 추가
   remark: z.string().optional(),
 })
 
@@ -125,8 +115,6 @@ export function CreatePreQuoteRfqDialog({
   onSuccess
 }: CreatePreQuoteRfqDialogProps) {
   const [isLoading, setIsLoading] = React.useState(false)
-  const [previewCode, setPreviewCode] = React.useState("")
-  const [isLoadingPreview, setIsLoadingPreview] = React.useState(false)
   const [selectedBidPic, setSelectedBidPic] = React.useState<PurchaseGroupCodeWithUser | undefined>(undefined)
   const { data: session } = useSession()
 
@@ -143,6 +131,8 @@ export function CreatePreQuoteRfqDialog({
       materialName: item.materialInfo || "",
       quantity: item.quantity ? parseFloat(item.quantity) : 1,
       uom: item.quantityUnit || item.weightUnit || "EA",
+      totalWeight: item.totalWeight ? parseFloat(item.totalWeight) : null,
+      weightUnit: item.weightUnit || null,
       remark: "",
     }))
   }, [biddingItems])
@@ -164,6 +154,8 @@ export function CreatePreQuoteRfqDialog({
           materialName: "",
           quantity: 1,
           uom: "",
+          totalWeight: null,
+          weightUnit: null,
           remark: "",
         },
       ],
@@ -231,6 +223,14 @@ export function CreatePreQuoteRfqDialog({
               const pName = bidding.projectName || "";
               setProjectInfo(pCode && pName ? `${pCode} - ${pName}` : pCode || pName || "");
 
+              // 프로젝트 ID 조회
+              if (pCode && pName) {
+                const fetchedProjectId = await getProjectIdByCodeAndName(pCode, pName)
+                if (fetchedProjectId) {
+                  form.setValue("projectId", fetchedProjectId)
+                }
+              }
+
               // 폼 값 설정
               form.setValue("rfqTitle", rfqTitle);
               form.setValue("rfqType", "pre_bidding"); // 기본값 설정
@@ -264,36 +264,15 @@ export function CreatePreQuoteRfqDialog({
             materialName: "",
             quantity: 1,
             uom: "",
+            totalWeight: null,
+            weightUnit: null,
             remark: "",
           },
         ],
       })
-      setPreviewCode("")
     }
   }, [open, initialItems, form, selectedBidPic, biddingId])
 
-  // 견적담당자 선택 시 RFQ 코드 미리보기 생성
-  React.useEffect(() => {
-    if (!selectedBidPic?.user?.id) {
-      setPreviewCode("")
-      return
-    }
-
-    // 즉시 실행 함수 패턴 사용
-    (async () => {
-      setIsLoadingPreview(true)
-      try {
-        const code = await previewGeneralRfqCode(selectedBidPic.user!.id)
-        setPreviewCode(code)
-      } catch (error) {
-        console.error("코드 미리보기 오류:", error)
-        setPreviewCode("")
-      } finally {
-        setIsLoadingPreview(false)
-      }
-    })()
-  }, [selectedBidPic])
-
   // 견적 종류 변경
   const handleRfqTypeChange = (value: string) => {
     form.setValue("rfqType", value)
@@ -315,12 +294,13 @@ export function CreatePreQuoteRfqDialog({
           materialName: "",
           quantity: 1,
           uom: "",
+          totalWeight: null,
+          weightUnit: null,
           remark: "",
         },
       ],
     })
       setSelectedBidPic(undefined)
-      setPreviewCode("")
     onOpenChange(false)
   }
 
@@ -350,15 +330,17 @@ export function CreatePreQuoteRfqDialog({
         biddingNumber: data.biddingNumber, // 추가
         contractStartDate: data.contractStartDate, // 추가
         contractEndDate: data.contractEndDate, // 추가
-        items: data.items as Array<{
-          itemCode: string;
-          itemName: string;
-          materialCode?: string;
-          materialName?: string;
-          quantity: number;
-          uom: string;
-          remark?: string;
-        }>,
+        items: data.items.map(item => ({
+          itemCode: item.itemCode || "",
+          itemName: item.itemName || "",
+          materialCode: item.materialCode,
+          materialName: item.materialName,
+          quantity: item.quantity,
+          uom: item.uom,
+          totalWeight: item.totalWeight,
+          weightUnit: item.weightUnit,
+          remark: item.remark,
+        })),
         biddingConditions: biddingConditions || undefined,
         createdBy: userId,
         updatedBy: userId,
@@ -590,17 +572,7 @@ export function CreatePreQuoteRfqDialog({
                     </FormItem>
                   )}
                 />
-                {/* RFQ 코드 미리보기 */}
-                {previewCode && (
-                  <div className="flex items-center gap-2">
-                    <Badge variant="secondary" className="font-mono text-sm">
-                      예상 RFQ 코드: {previewCode}
-                    </Badge>
-                    {isLoadingPreview && (
-                      <Loader2 className="h-3 w-3 animate-spin" />
-                    )}
-                  </div>
-                )}
+
 
                 {/* 계약기간 */}
                 <div className="grid grid-cols-2 gap-4">
diff --git a/lib/bidding/actions.ts b/lib/bidding/actions.ts
index cc246ee7..6bedbab5 100644
--- a/lib/bidding/actions.ts
+++ b/lib/bidding/actions.ts
@@ -652,7 +652,7 @@ export async function cancelDisposalAction(
 }
 
 // 사용자 이름 조회 헬퍼 함수
-async function getUserNameById(userId: string): Promise<string> {
+export async function getUserNameById(userId: string): Promise<string> {
   try {
     const user = await db
       .select({ name: users.name })
diff --git a/lib/bidding/approval-actions.ts b/lib/bidding/approval-actions.ts
index 0fb16439..b4f6f297 100644
--- a/lib/bidding/approval-actions.ts
+++ b/lib/bidding/approval-actions.ts
@@ -266,12 +266,14 @@ export async function requestBiddingInvitationWithApproval(data: {
   const { default: db } = await import('@/db/db');
   const { biddings, biddingCompanies, prItemsForBidding } = await import('@/db/schema');
   const { eq } = await import('drizzle-orm');
-
+  const { getUserNameById } = await import('@/lib/bidding/actions');
+  const userName = await getUserNameById(data.currentUser.id.toString());
+  
   await db
     .update(biddings)
     .set({
       status: 'approval_pending', // 결재 진행중 상태
-      // updatedBy: String(data.currentUser.id), // 기존 등록자 유지를 위해 주석 처리
+      updatedBy: userName,
       updatedAt: new Date()
     })
     .where(eq(biddings.id, data.biddingId));
@@ -465,6 +467,7 @@ export async function requestBiddingClosureWithApproval(data: {
   const { default: db } = await import('@/db/db');
   const { biddings } = await import('@/db/schema');
   const { eq } = await import('drizzle-orm');
+  const { getUserNameById } = await import('@/lib/bidding/actions');
 
   // 유찰상태인지 확인
   const biddingResult = await db
@@ -487,12 +490,12 @@ export async function requestBiddingClosureWithApproval(data: {
 
   // 3. 입찰 상태를 결재 진행중으로 변경
   debugLog('[BiddingClosureApproval] 입찰 상태 변경 시작');
-
+  const userName = await getUserNameById(data.currentUser.id.toString());
   await db
     .update(biddings)
     .set({
       status: 'approval_pending', // 폐찰 결재 진행중 상태
-      // updatedBy: Number(data.currentUser.id), // 기존 등록자 유지를 위해 주석 처리
+      updatedBy: userName,
       updatedAt: new Date()
     })
     .where(eq(biddings.id, data.biddingId));
@@ -693,12 +696,13 @@ export async function requestBiddingAwardWithApproval(data: {
   const { default: db } = await import('@/db/db');
   const { biddings } = await import('@/db/schema');
   const { eq } = await import('drizzle-orm');
-
+  const { getUserNameById } = await import('@/lib/bidding/actions');
+  const userName = await getUserNameById(data.currentUser.id.toString());
   await db
     .update(biddings)
     .set({
       status: 'approval_pending', // 낙찰 결재 진행중 상태
-      // updatedBy: Number(data.currentUser.id), // 기존 등록자 유지를 위해 주석 처리
+      updatedBy: userName,
       updatedAt: new Date()
     })
     .where(eq(biddings.id, data.biddingId));
diff --git a/lib/bidding/detail/service.ts b/lib/bidding/detail/service.ts
index 68c55fb0..17ea8f28 100644
--- a/lib/bidding/detail/service.ts
+++ b/lib/bidding/detail/service.ts
@@ -1288,10 +1288,14 @@ export async function getAwardedCompanies(biddingId: number) {
         companyId: biddingCompanies.companyId,
         companyName: vendors.vendorName,
         finalQuoteAmount: biddingCompanies.finalQuoteAmount,
-        awardRatio: biddingCompanies.awardRatio
+        awardRatio: biddingCompanies.awardRatio,
+        vendorCode: vendors.vendorCode,
+        companySize: vendors.businessSize,
+        targetPrice: biddings.targetPrice
       })
       .from(biddingCompanies)
       .leftJoin(vendors, eq(biddingCompanies.companyId, vendors.id))
+      .leftJoin(biddings, eq(biddingCompanies.biddingId, biddings.id))
       .where(and(
         eq(biddingCompanies.biddingId, biddingId),
         eq(biddingCompanies.isWinner, true)
@@ -1301,7 +1305,10 @@ export async function getAwardedCompanies(biddingId: number) {
       companyId: company.companyId,
       companyName: company.companyName,
       finalQuoteAmount: parseFloat(company.finalQuoteAmount?.toString() || '0'),
-      awardRatio: parseFloat(company.awardRatio?.toString() || '0')
+      awardRatio: parseFloat(company.awardRatio?.toString() || '0'),
+      vendorCode: company.vendorCode,
+      companySize: company.companySize,
+      targetPrice: company.targetPrice ? parseFloat(company.targetPrice.toString()) : 0
     }))
   } catch (error) {
     console.error('Failed to get awarded companies:', error)
@@ -1330,7 +1337,7 @@ async function updateBiddingAmounts(biddingId: number) {
       .set({
         targetPrice: totalTargetAmount.toString(),
         budget: totalBudgetAmount.toString(),
-        finalBidPrice: totalActualAmount.toString(),
+        actualPrice: totalActualAmount.toString(),
         updatedAt: new Date()
       })
       .where(eq(biddings.id, biddingId))
@@ -1745,7 +1752,6 @@ export async function getBiddingDetailsForPartners(biddingId: number, companyId:
         biddingRegistrationDate: biddings.biddingRegistrationDate,
         submissionStartDate: biddings.submissionStartDate,
         submissionEndDate: biddings.submissionEndDate,
-        evaluationDate: biddings.evaluationDate,
 
         // 가격 정보
         currency: biddings.currency,
diff --git a/lib/bidding/handlers.ts b/lib/bidding/handlers.ts
index d56a083a..03a85bb6 100644
--- a/lib/bidding/handlers.ts
+++ b/lib/bidding/handlers.ts
@@ -422,12 +422,13 @@ export async function requestBiddingClosureInternal(payload: {
     const { default: db } = await import('@/db/db');
     const { biddings } = await import('@/db/schema');
     const { eq } = await import('drizzle-orm');
-
+    const { getUserNameById } = await import('@/lib/bidding/actions');
+    const userName = await getUserNameById(payload.currentUserId.toString());
     await db
       .update(biddings)
       .set({
         status: 'bid_closure',
-        updatedBy: payload.currentUserId.toString(),
+        updatedBy: userName,
         updatedAt: new Date(),
         remarks: payload.description, // 폐찰 사유를 remarks에 저장
       })
@@ -614,6 +615,15 @@ export async function mapBiddingAwardToTemplateVariables(payload: {
   biddingId: number;
   selectionReason: string;
   requestedAt: Date;
+  awardedCompanies?: Array<{
+    companyId: number;
+    companyName: string | null;
+    finalQuoteAmount: number;
+    awardRatio: number;
+    vendorCode?: string | null;
+    companySize?: string | null;
+    targetPrice?: number | null;
+  }>;
 }): Promise<Record<string, string>> {
   const { biddingId, selectionReason, requestedAt } = payload;
 
@@ -649,8 +659,11 @@ export async function mapBiddingAwardToTemplateVariables(payload: {
   const bidding = biddingInfo[0];
 
   // 2. 낙찰된 업체 정보 조회
-  const { getAwardedCompanies } = await import('@/lib/bidding/detail/service');
-  const awardedCompanies = await getAwardedCompanies(biddingId);
+  let awardedCompanies = payload.awardedCompanies;
+  if (!awardedCompanies) {
+    const { getAwardedCompanies } = await import('@/lib/bidding/detail/service');
+    awardedCompanies = await getAwardedCompanies(biddingId);
+  }
 
   // 3. 입찰 대상 자재 정보 조회
   const biddingItemsInfo = await db
diff --git a/lib/bidding/manage/import-bidding-items-from-excel.ts b/lib/bidding/manage/import-bidding-items-from-excel.ts
index 2e0dfe33..fe5b17a9 100644
--- a/lib/bidding/manage/import-bidding-items-from-excel.ts
+++ b/lib/bidding/manage/import-bidding-items-from-excel.ts
@@ -1,6 +1,7 @@
 import ExcelJS from "exceljs"
 import { PRItemInfo } from "@/components/bidding/manage/bidding-items-editor"
 import { getProjectIdByCodeAndName } from "./project-utils"
+import { decryptWithServerAction } from "@/components/drm/drmUtils"
 
 export interface ImportBiddingItemsResult {
   success: boolean
@@ -19,7 +20,8 @@ export async function importBiddingItemsFromExcel(
 
   try {
     const workbook = new ExcelJS.Workbook()
-    const arrayBuffer = await file.arrayBuffer()
+    // DRM 해제 후 ArrayBuffer 획득 (DRM 서버 미연결 시 원본 반환)
+    const arrayBuffer = await decryptWithServerAction(file)
     await workbook.xlsx.load(arrayBuffer)
 
     const worksheet = workbook.worksheets[0]
diff --git a/lib/bidding/pre-quote/service.ts b/lib/bidding/pre-quote/service.ts
index e1152abe..6fef228c 100644
--- a/lib/bidding/pre-quote/service.ts
+++ b/lib/bidding/pre-quote/service.ts
@@ -859,8 +859,8 @@ export async function getSelectedVendorsForBidding(biddingId: number) {
 interface CreatePreQuoteRfqInput {
   rfqType: string;
   rfqTitle: string;
-  dueDate: Date;
-  picUserId: number;
+  dueDate?: Date;
+  picUserId: number | string | undefined;
   projectId?: number;
   remark?: string;
   biddingNumber?: string;
@@ -875,6 +875,8 @@ interface CreatePreQuoteRfqInput {
     remark?: string;
     materialCode?: string;
     materialName?: string;
+    totalWeight?: number | string | null; // 중량 추가
+    weightUnit?: string | null; // 중량단위 추가
   }>;
   biddingConditions?: {
     paymentTerms?: string | null
@@ -976,6 +978,10 @@ export async function createPreQuoteRfqAction(input: CreatePreQuoteRfqInput) {
         quantity: item.quantity, // 수량
         uom: item.uom, // 단위
 
+        // 중량 정보
+        grossWeight: item.totalWeight ? (typeof item.totalWeight === 'string' ? parseFloat(item.totalWeight) : item.totalWeight) : null,
+        gwUom: item.weightUnit || null,
+
         majorYn: index === 0, // 첫 번째 아이템을 주요 아이템으로 설정
         remark: item.remark || null, // 비고
       }));
diff --git a/lib/bidding/service.ts b/lib/bidding/service.ts
index 77a0b1b4..76cd31f7 100644
--- a/lib/bidding/service.ts
+++ b/lib/bidding/service.ts
@@ -61,6 +61,27 @@ export async function getUserCodeByEmail(email: string): Promise<string | null>
   }
 }
 
+// 사용자 ID로 상세 정보 조회 (이름, 코드 등)
+export async function getUserDetails(userId: number) {
+  try {
+    const user = await db
+      .select({
+        id: users.id,
+        name: users.name,
+        userCode: users.userCode,
+        employeeNumber: users.employeeNumber
+      })
+      .from(users)
+      .where(eq(users.id, userId))
+      .limit(1)
+
+    return user[0] || null
+  } catch (error) {
+    console.error('Failed to get user details:', error)
+    return null
+  }
+}
+
 // userId를 user.name으로 변환하는 유틸리티 함수
 async function getUserNameById(userId: string): Promise<string> {
   try {
@@ -421,9 +442,10 @@ export async function getBiddings(input: GetBiddingsSchema) {
                 // 메타 정보
                 remarks: biddings.remarks,
                 updatedAt: biddings.updatedAt,
-                updatedBy: biddings.updatedBy,
+                updatedBy: users.name,
             })
             .from(biddings)
+            .leftJoin(users, sql`${biddings.updatedBy} = ${users.id}::varchar`)
             .where(finalWhere)
             .orderBy(...orderByColumns)
             .limit(input.perPage)
@@ -874,7 +896,6 @@ export async function createBidding(input: CreateBiddingInput, userId: string) {
             biddingRegistrationDate: new Date(),
             submissionStartDate: parseDate(input.submissionStartDate),
             submissionEndDate: parseDate(input.submissionEndDate),
-            evaluationDate: parseDate(input.evaluationDate),
             
             hasSpecificationMeeting: input.hasSpecificationMeeting || false,
             hasPrDocument: input.hasPrDocument || false,
@@ -913,6 +934,7 @@ export async function createBidding(input: CreateBiddingInput, userId: string) {
         await tx.insert(biddingNoticeTemplate).values({
           biddingId,
           title: input.title + ' 입찰공고',
+          type: input.noticeType || 'standard',
           content: input.content || standardContent,
           isTemplate: false,
         })
@@ -1723,7 +1745,6 @@ export async function updateBiddingBasicInfo(
     contractEndDate?: string
     submissionStartDate?: string
     submissionEndDate?: string
-    evaluationDate?: string
     hasSpecificationMeeting?: boolean
     hasPrDocument?: boolean
     currency?: string
@@ -1781,9 +1802,23 @@ export async function updateBiddingBasicInfo(
     // 정의된 필드들만 업데이트
     if (updates.title !== undefined) updateData.title = updates.title
     if (updates.description !== undefined) updateData.description = updates.description
-    if (updates.content !== undefined) updateData.content = updates.content
+    // content는 bidding 테이블에 컬럼이 없음, notice content는 별도로 저장해야 함
+    // if (updates.content !== undefined) updateData.content = updates.content
     if (updates.noticeType !== undefined) updateData.noticeType = updates.noticeType
     if (updates.contractType !== undefined) updateData.contractType = updates.contractType
+
+    // 입찰공고 내용 저장
+    if (updates.content !== undefined) {
+      try {
+        await saveBiddingNotice(biddingId, {
+          title: (updates.title || '') + ' 입찰공고', // 제목이 없으면 기존 제목을 가져오거나 해야하는데, 여기서는 업데이트된 제목 사용
+          content: updates.content
+        })
+      } catch (e) {
+        console.error('Failed to save bidding notice content:', e)
+        // 공고 저장 실패는 전체 업데이트 실패로 처리하지 않음 (로그만 남김)
+      }
+    }
     if (updates.biddingType !== undefined) updateData.biddingType = updates.biddingType
     if (updates.biddingTypeCustom !== undefined) updateData.biddingTypeCustom = updates.biddingTypeCustom
     if (updates.awardCount !== undefined) updateData.awardCount = updates.awardCount
@@ -1795,7 +1830,6 @@ export async function updateBiddingBasicInfo(
     if (updates.contractEndDate !== undefined) updateData.contractEndDate = parseDate(updates.contractEndDate)
     if (updates.submissionStartDate !== undefined) updateData.submissionStartDate = parseDate(updates.submissionStartDate)
     if (updates.submissionEndDate !== undefined) updateData.submissionEndDate = parseDate(updates.submissionEndDate)
-    if (updates.evaluationDate !== undefined) updateData.evaluationDate = parseDate(updates.evaluationDate)
     if (updates.hasSpecificationMeeting !== undefined) updateData.hasSpecificationMeeting = updates.hasSpecificationMeeting
     if (updates.hasPrDocument !== undefined) updateData.hasPrDocument = updates.hasPrDocument
     if (updates.currency !== undefined) updateData.currency = updates.currency
@@ -2889,7 +2923,7 @@ export async function increaseRoundOrRebid(biddingId: number, userId: string | u
         let currentRound = match ? parseInt(match[1]) : 1
 
         if (currentRound >= 3) {
-          // -03 이상이면 새로운 번호 생성
+          // -03 이상이면 재입찰이며, 새로운 번호 생성
           newBiddingNumber = await generateBiddingNumber(existingBidding.contractType, userId, tx)
           // 새로 생성한 입찰번호를 원입찰번호로 셋팅
           originalBiddingNumber = newBiddingNumber.split('-')[0]
@@ -2913,13 +2947,15 @@ export async function increaseRoundOrRebid(biddingId: number, userId: string | u
           
           // 기본 정보 복제
           projectName: existingBidding.projectName,
+          projectCode: existingBidding.projectCode, // 프로젝트 코드 복제
           itemName: existingBidding.itemName,
           title: existingBidding.title,
           description: existingBidding.description,
           
           // 계약 정보 복제
           contractType: existingBidding.contractType,
-          biddingType: existingBidding.biddingType,
+          noticeType: existingBidding.noticeType, // 공고타입 복제
+          biddingType: existingBidding.biddingType, // 구매유형 복제
           awardCount: existingBidding.awardCount,
           contractStartDate: existingBidding.contractStartDate,
           contractEndDate: existingBidding.contractEndDate,
@@ -2929,7 +2965,6 @@ export async function increaseRoundOrRebid(biddingId: number, userId: string | u
           biddingRegistrationDate: new Date(),
           submissionStartDate: null,
           submissionEndDate: null,
-          evaluationDate: null,
           
           // 사양설명회
           hasSpecificationMeeting: existingBidding.hasSpecificationMeeting,
@@ -2939,6 +2974,7 @@ export async function increaseRoundOrRebid(biddingId: number, userId: string | u
           budget: existingBidding.budget,
           targetPrice: existingBidding.targetPrice,
           targetPriceCalculationCriteria: existingBidding.targetPriceCalculationCriteria,
+          actualPrice: existingBidding.actualPrice,
           finalBidPrice: null, // 최종입찰가는 초기화
           
           // PR 정보 복제
@@ -3194,8 +3230,6 @@ export async function increaseRoundOrRebid(biddingId: number, userId: string | u
         .from(biddingDocuments)
         .where(and(
           eq(biddingDocuments.biddingId, biddingId),
-          // PR 아이템에 연결된 첨부파일은 제외 (SHI용과 협력업체용만 복제)
-          isNull(biddingDocuments.prItemId),
           // SHI용(evaluation_doc) 또는 협력업체용(company_proposal) 문서만 복제
           or(
             eq(biddingDocuments.documentType, 'evaluation_doc'),
@@ -3266,6 +3300,8 @@ export async function increaseRoundOrRebid(biddingId: number, userId: string | u
       }
 
       revalidatePath('/bid-receive')
+      revalidatePath('/evcp/bid-receive')
+      revalidatePath('/evcp/bid')
       revalidatePath(`/bid-receive/${biddingId}`) // 기존 입찰 페이지도 갱신
       revalidatePath(`/bid-receive/${newBidding.id}`)
 
@@ -3825,7 +3861,7 @@ export async function getBiddingsForFailure(input: GetBiddingsSchema) {
         // 유찰 정보 (업데이트 일시를 유찰일로 사용)
         disposalDate: biddings.updatedAt, // 유찰일
         disposalUpdatedAt: biddings.updatedAt, // 폐찰수정일
-        disposalUpdatedBy: biddings.updatedBy, // 폐찰수정자
+        disposalUpdatedBy: users.name, // 폐찰수정자
 
         // 폐찰 정보
         closureReason: biddings.description, // 폐찰사유
@@ -3840,9 +3876,10 @@ export async function getBiddingsForFailure(input: GetBiddingsSchema) {
         createdBy: biddings.createdBy,
         createdAt: biddings.createdAt,
         updatedAt: biddings.updatedAt,
-        updatedBy: biddings.updatedBy,
+        updatedBy: users.name,
       })
       .from(biddings)
+      .leftJoin(users, sql`${biddings.updatedBy} = ${users.id}::varchar`)
       .leftJoin(biddingDocuments, and(
         eq(biddingDocuments.biddingId, biddings.id),
         eq(biddingDocuments.documentType, 'evaluation_doc'), // 폐찰 문서
diff --git a/lib/bidding/validation.ts b/lib/bidding/validation.ts
index 73c2fe21..3254ae7e 100644
--- a/lib/bidding/validation.ts
+++ b/lib/bidding/validation.ts
@@ -99,7 +99,6 @@ export const createBiddingSchema = z.object({
 
     submissionEndDate: z.string().optional(),
 
-    evaluationDate: z.string().optional(),
 
     // 회의 및 문서
     hasSpecificationMeeting: z.boolean().default(false),
@@ -220,7 +219,6 @@ export const createBiddingSchema = z.object({
 
     submissionStartDate: z.string().optional(),
     submissionEndDate: z.string().optional(),
-    evaluationDate: z.string().optional(),
 
     hasSpecificationMeeting: z.boolean().optional(),
     hasPrDocument: z.boolean().optional(),
diff --git a/lib/bidding/vendor/partners-bidding-attendance-dialog.tsx b/lib/bidding/vendor/partners-bidding-attendance-dialog.tsx
index d0ef97f1..8d6cb82d 100644
--- a/lib/bidding/vendor/partners-bidding-attendance-dialog.tsx
+++ b/lib/bidding/vendor/partners-bidding-attendance-dialog.tsx
@@ -37,7 +37,6 @@ interface PartnersSpecificationMeetingDialogProps {
     title: string
     preQuoteDate: string | null
     biddingRegistrationDate: string | null
-    evaluationDate: string | null
     hasSpecificationMeeting?: boolean // 사양설명회 여부 추가
   } | null
   biddingCompanyId: number
diff --git a/lib/bidding/vendor/partners-bidding-detail.tsx b/lib/bidding/vendor/partners-bidding-detail.tsx
index bf76de62..087648ab 100644
--- a/lib/bidding/vendor/partners-bidding-detail.tsx
+++ b/lib/bidding/vendor/partners-bidding-detail.tsx
@@ -75,7 +75,6 @@ interface BiddingDetail {
   biddingRegistrationDate: Date | string | null
   submissionStartDate: Date | string | null
   submissionEndDate: Date | string | null
-  evaluationDate: Date | string | null
   currency: string
   budget: number | null
   targetPrice: number | null
@@ -927,11 +926,7 @@ export function PartnersBiddingDetail({ biddingId, companyId }: PartnersBiddingD
                   })()}
                 </div>
               )}
-              {biddingDetail.evaluationDate && (
-                <div>
-                  <span className="font-medium">평가일:</span> {format(new Date(biddingDetail.evaluationDate), "yyyy-MM-dd HH:mm")}
-                </div>
-              )}
+
             </div>
           </div>
         </CardContent>
diff --git a/lib/bidding/vendor/partners-bidding-list.tsx b/lib/bidding/vendor/partners-bidding-list.tsx
index 0f68ed68..f1cb0bdc 100644
--- a/lib/bidding/vendor/partners-bidding-list.tsx
+++ b/lib/bidding/vendor/partners-bidding-list.tsx
@@ -181,7 +181,6 @@ export function PartnersBiddingList({ promises }: PartnersBiddingListProps) {
           title: rowAction.row.original.title,
           preQuoteDate: null,
           biddingRegistrationDate: rowAction.row.original.submissionStartDate?.toISOString() || null,
-          evaluationDate: null,
           hasSpecificationMeeting: rowAction.row.original.hasSpecificationMeeting || false,
         } : null}
         biddingCompanyId={rowAction?.row.original?.biddingCompanyId || 0}
-- 
cgit v1.2.3

