From b9a2081a76e669688d5884f20482b37cc8acca22 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 13 Oct 2025 08:56:27 +0000 Subject: (최겸, 임수민) 구매 입찰, 견적(그룹코드, tbe에러) 수정, data-room 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bidding/list/biddings-stats-cards.tsx | 4 +- lib/bidding/list/biddings-table-columns.tsx | 34 +- lib/bidding/list/biddings-table.tsx | 5 +- lib/bidding/list/create-bidding-dialog.tsx | 650 ++++++++++----------- lib/bidding/list/edit-bidding-sheet.tsx | 4 +- lib/bidding/service.ts | 16 + lib/bidding/validation.ts | 17 +- lib/rfq-last/attachment/rfq-attachments-table.tsx | 6 +- lib/rfq-last/service.ts | 29 + .../vendor/batch-update-conditions-dialog.tsx | 69 ++- lib/rfq-last/vendor/vendor-detail-dialog.tsx | 10 + lib/soap/ecc/mapper/bidding-and-pr-mapper.ts | 12 +- lib/soap/ecc/mapper/common-mapper-utils.ts | 7 +- 13 files changed, 473 insertions(+), 390 deletions(-) (limited to 'lib') diff --git a/lib/bidding/list/biddings-stats-cards.tsx b/lib/bidding/list/biddings-stats-cards.tsx index 2926adac..14e29c16 100644 --- a/lib/bidding/list/biddings-stats-cards.tsx +++ b/lib/bidding/list/biddings-stats-cards.tsx @@ -60,9 +60,9 @@ export function BiddingsStatsCards({
{total.toLocaleString()}
-

+ {/*

이번 달 +{thisMonthCount}건 -

+

*/}
diff --git a/lib/bidding/list/biddings-table-columns.tsx b/lib/bidding/list/biddings-table-columns.tsx index 7f0b8e40..4900d18a 100644 --- a/lib/bidding/list/biddings-table-columns.tsx +++ b/lib/bidding/list/biddings-table-columns.tsx @@ -5,6 +5,7 @@ import { type ColumnDef } from "@tanstack/react-table" import { Checkbox } from "@/components/ui/checkbox" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" +import { getUserCodeByEmail } from "@/lib/bidding/service" import { Eye, Edit, MoreHorizontal, FileText, Users, Calendar, Building, Package, DollarSign, Clock, CheckCircle, XCircle, @@ -26,6 +27,12 @@ import { import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" import { BiddingListItem } from "@/db/schema" import { DataTableRowAction } from "@/types/table" + +// BiddingListItem에 manager 정보 추가 +type BiddingListItemWithManagerCode = BiddingListItem & { + managerName?: string | null + managerCode?: string | null +} import { biddingStatusLabels, contractTypeLabels, @@ -35,7 +42,7 @@ import { import { formatDate } from "@/lib/utils" interface GetColumnsProps { - setRowAction: React.Dispatch | null>> + setRowAction: React.Dispatch | null>> } // 상태별 배지 색상 @@ -78,7 +85,8 @@ const formatCurrency = (amount: string | number | null, currency = 'KRW') => { -export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef[] { +export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef[] { + return [ // ═══════════════════════════════════════════════════════════════ // 선택 및 기본 정보 @@ -191,11 +199,11 @@ export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef { accessorKey: "managerName", header: ({ column }) => , - cell: ({ row }) => ( -
- {row.original.managerName || '-'} -
- ), + cell: ({ row }) => { + const name = row.original.managerName || "-"; + const managerCode = row.original.managerCode || ""; + return name === "-" ? "-" : `${name}(${managerCode})`; + }, size: 100, meta: { excelHeader: "입찰담당자" }, }, @@ -237,10 +245,12 @@ export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef
), @@ -394,7 +404,7 @@ export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef header: ({ column }) => , cell: ({ row }) => ( - {formatCurrency(row.original.budget, row.original.currency)} + {row.original.budget} ), size: 120, @@ -406,7 +416,7 @@ export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef header: ({ column }) => , cell: ({ row }) => ( - {formatCurrency(row.original.targetPrice, row.original.currency)} + {row.original.targetPrice} ), size: 120, @@ -418,7 +428,7 @@ export function getBiddingsColumns({ setRowAction }: GetColumnsProps): ColumnDef header: ({ column }) => , cell: ({ row }) => ( - {formatCurrency(row.original.finalBidPrice, row.original.currency)} + {row.original.finalBidPrice} ), size: 120, diff --git a/lib/bidding/list/biddings-table.tsx b/lib/bidding/list/biddings-table.tsx index 2ecfaa73..8920d9db 100644 --- a/lib/bidding/list/biddings-table.tsx +++ b/lib/bidding/list/biddings-table.tsx @@ -14,6 +14,7 @@ import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-adv import { getBiddingsColumns } from "./biddings-table-columns" import { getBiddings, getBiddingStatusCounts } from "@/lib/bidding/service" import { BiddingListItem } from "@/db/schema" +import { BiddingListItemWithManagerCode } from "./biddings-table-columns" import { BiddingsTableToolbarActions } from "./biddings-table-toolbar-actions" import { biddingStatusLabels, @@ -42,11 +43,11 @@ export function BiddingsTable({ promises }: BiddingsTableProps) { const [isCompact, setIsCompact] = React.useState(false) const [specMeetingDialogOpen, setSpecMeetingDialogOpen] = React.useState(false) const [prDocumentsDialogOpen, setPrDocumentsDialogOpen] = React.useState(false) - const [selectedBidding, setSelectedBidding] = React.useState(null) + const [selectedBidding, setSelectedBidding] = React.useState(null) console.log(data,"data") - const [rowAction, setRowAction] = React.useState | null>(null) + const [rowAction, setRowAction] = React.useState | null>(null) const router = useRouter() diff --git a/lib/bidding/list/create-bidding-dialog.tsx b/lib/bidding/list/create-bidding-dialog.tsx index e99ac06f..50246f58 100644 --- a/lib/bidding/list/create-bidding-dialog.tsx +++ b/lib/bidding/list/create-bidding-dialog.tsx @@ -126,7 +126,7 @@ interface PRItemInfo { } // 탭 순서 정의 -const TAB_ORDER = ["basic", "contract", "schedule", "conditions", "details", "manager"] as const +const TAB_ORDER = ["basic", "schedule", "details", "manager"] as const type TabType = typeof TAB_ORDER[number] export function CreateBiddingDialog() { @@ -184,11 +184,11 @@ export function CreateBiddingDialog() { // 파일 첨부를 위해 선택된 아이템 ID const [selectedItemForFile, setSelectedItemForFile] = React.useState(null) - // 입찰 조건 상태 + // 입찰 조건 상태 (기본값 설정 포함) const [biddingConditions, setBiddingConditions] = React.useState({ - paymentTerms: "", - taxConditions: "", - incoterms: "", + paymentTerms: "", // 초기값 빈값, 데이터 로드 후 설정 + taxConditions: "", // 초기값 빈값, 데이터 로드 후 설정 + incoterms: "", // 초기값 빈값, 데이터 로드 후 설정 contractDeliveryDate: "", shippingPort: "", destinationPort: "", @@ -202,26 +202,49 @@ export function CreateBiddingDialog() { try { const data = await getPaymentTermsForSelection(); setPaymentTermsOptions(data); + // 기본값 설정 로직: P008이 있으면 P008로, 없으면 첫 번째 항목으로 설정 + const setDefaultPaymentTerms = () => { + const p008Exists = data.some(item => item.code === "P008"); + if (p008Exists) { + setBiddingConditions(prev => ({ ...prev, paymentTerms: "P008" })); + } + }; + + setDefaultPaymentTerms(); } catch (error) { console.error("Failed to load payment terms:", error); toast.error("결제조건 목록을 불러오는데 실패했습니다."); + // 에러 시 기본값 초기화 + if (biddingConditions.paymentTerms === "P008") { + setBiddingConditions(prev => ({ ...prev, paymentTerms: "" })); + } } finally { setProcurementLoading(false); } - }, []); + }, [biddingConditions.paymentTerms]); const loadIncoterms = React.useCallback(async () => { setProcurementLoading(true); try { const data = await getIncotermsForSelection(); setIncotermsOptions(data); + + // 기본값 설정 로직: DAP가 있으면 DAP로, 없으면 첫 번째 항목으로 설정 + const setDefaultIncoterms = () => { + const dapExists = data.some(item => item.code === "DAP"); + if (dapExists) { + setBiddingConditions(prev => ({ ...prev, incoterms: "DAP" })); + } + }; + + setDefaultIncoterms(); } catch (error) { console.error("Failed to load incoterms:", error); toast.error("운송조건 목록을 불러오는데 실패했습니다."); } finally { setProcurementLoading(false); } - }, []); + }, [biddingConditions.incoterms]); const loadShippingPlaces = React.useCallback(async () => { setProcurementLoading(true); @@ -249,13 +272,19 @@ export function CreateBiddingDialog() { } }, []); - // 다이얼로그 열릴 때 procurement 데이터 로드 + // 다이얼로그 열릴 때 procurement 데이터 로드 및 기본값 설정 React.useEffect(() => { if (open) { loadPaymentTerms(); loadIncoterms(); loadShippingPlaces(); loadDestinationPlaces(); + + // 세금조건 기본값 설정 (V1이 있는지 확인하고 설정) + const v1Exists = TAX_CONDITIONS.some(item => item.code === "V1"); + if (v1Exists) { + setBiddingConditions(prev => ({ ...prev, taxConditions: "V1" })); + } } }, [open, loadPaymentTerms, loadIncoterms, loadShippingPlaces, loadDestinationPlaces]) @@ -294,6 +323,7 @@ export function CreateBiddingDialog() { contractType: "general", biddingType: "equipment", + biddingTypeCustom: "", awardCount: "single", contractStartDate: "", contractEndDate: "", @@ -344,10 +374,8 @@ export function CreateBiddingDialog() { return { basic: { - isValid: formValues.projectId > 0 && - formValues.itemName.trim() !== "" && - formValues.title.trim() !== "", - hasErrors: !!(formErrors.projectId || formErrors.itemName || formErrors.title) + isValid: formValues.title.trim() !== "", + hasErrors: !!(formErrors.title) }, contract: { isValid: formValues.contractType && @@ -399,11 +427,18 @@ export function CreateBiddingDialog() { return representativeItem?.prNumber || "" }, [prItems]) - // hasPrDocument 필드와 prNumber를 자동으로 업데이트 + // 대표 품목명 자동 계산 (첫 번째 PR 아이템의 itemInfo) + const representativeItemName = React.useMemo(() => { + const representativeItem = prItems.find(item => item.isRepresentative) + return representativeItem?.itemInfo || "" + }, [prItems]) + + // hasPrDocument 필드와 prNumber, itemName을 자동으로 업데이트 React.useEffect(() => { form.setValue("hasPrDocument", hasPrDocuments) form.setValue("prNumber", representativePrNumber) - }, [hasPrDocuments, representativePrNumber, form]) + form.setValue("itemName", representativeItemName) + }, [hasPrDocuments, representativePrNumber, representativeItemName, form]) @@ -525,7 +560,7 @@ export function CreateBiddingDialog() { if (!isCurrentTabValid()) { // 특정 탭별 에러 메시지 if (activeTab === "basic") { - toast.error("기본 정보를 모두 입력해주세요 (프로젝트, 품목명, 입찰명)") + toast.error("기본 정보를 모두 입력해주세요 (품목명, 입찰명)") } else if (activeTab === "contract") { toast.error("계약 정보를 모두 입력해주세요") } else if (activeTab === "schedule") { @@ -535,7 +570,7 @@ export function CreateBiddingDialog() { toast.error("제출 시작일시와 마감일시를 입력해주세요") } } else if (activeTab === "conditions") { - toast.error("입찰 조건을 모두 입력해주세요 (지급조건, 세금조건, 운송조건, 계약납품일, 선적지, 하역지)") + toast.error("입찰 조건을 모두 입력해주세요 (지급조건, 세금조건, 운송조건, 계약납품일)") } else if (activeTab === "details") { toast.error("품목정보, 수량/단위 또는 중량/중량단위를 입력해주세요") } @@ -617,6 +652,7 @@ export function CreateBiddingDialog() { content: "", contractType: "general", biddingType: "equipment", + biddingTypeCustom: "", awardCount: "single", contractStartDate: "", contractEndDate: "", @@ -625,9 +661,6 @@ export function CreateBiddingDialog() { hasSpecificationMeeting: false, prNumber: "", currency: "KRW", - budget: "", - targetPrice: "", - finalBidPrice: "", status: "bidding_generated", isPublic: false, managerName: "", @@ -778,20 +811,6 @@ export function CreateBiddingDialog() { )} - -