summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-12-05 03:28:04 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-12-05 03:28:04 +0000
commit93b6b8868d409c7f6c9d9222b93750848caaedde (patch)
treef3118e0afed688144cad9ea1ce9da6c1909781be
parent8ee21b4e9691b4f72e333af8a61918ffd869404a (diff)
(최겸) 구매 입찰 수정
-rw-r--r--components/bidding/create/bidding-create-dialog.tsx51
-rw-r--r--components/bidding/manage/bidding-basic-info-editor.tsx2
-rw-r--r--components/bidding/manage/bidding-companies-editor.tsx17
-rw-r--r--components/bidding/manage/bidding-detail-vendor-create-dialog.tsx15
-rw-r--r--components/bidding/manage/create-pre-quote-rfq-dialog.tsx90
-rw-r--r--lib/bidding/actions.ts2
-rw-r--r--lib/bidding/approval-actions.ts16
-rw-r--r--lib/bidding/detail/service.ts14
-rw-r--r--lib/bidding/handlers.ts21
-rw-r--r--lib/bidding/manage/import-bidding-items-from-excel.ts4
-rw-r--r--lib/bidding/pre-quote/service.ts10
-rw-r--r--lib/bidding/service.ts61
-rw-r--r--lib/bidding/validation.ts2
-rw-r--r--lib/bidding/vendor/partners-bidding-attendance-dialog.tsx1
-rw-r--r--lib/bidding/vendor/partners-bidding-detail.tsx7
-rw-r--r--lib/bidding/vendor/partners-bidding-list.tsx1
16 files changed, 196 insertions, 118 deletions
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}