diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-11 09:22:58 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-11 09:22:58 +0000 |
| commit | 535de26b9cf3242c04543d6891d78352b9593a60 (patch) | |
| tree | cfb2a76a3442ad0ec1d2b2b75293658d93a45808 /lib/rfq-last | |
| parent | 88b737a71372353e47c466553273d88f5bf36834 (diff) | |
(최겸) 구매 수정사항 개발
Diffstat (limited to 'lib/rfq-last')
| -rw-r--r-- | lib/rfq-last/attachment/vendor-response-table.tsx | 2 | ||||
| -rw-r--r-- | lib/rfq-last/quotation-compare-view.tsx | 46 | ||||
| -rw-r--r-- | lib/rfq-last/table/rfq-table-columns.tsx | 2 | ||||
| -rw-r--r-- | lib/rfq-last/table/rfq-table.tsx | 2 | ||||
| -rw-r--r-- | lib/rfq-last/validations.ts | 2 | ||||
| -rw-r--r-- | lib/rfq-last/vendor/batch-update-conditions-dialog.tsx | 112 | ||||
| -rw-r--r-- | lib/rfq-last/vendor/rfq-vendor-table.tsx | 56 | ||||
| -rw-r--r-- | lib/rfq-last/vendor/send-rfq-dialog.tsx | 8 |
8 files changed, 90 insertions, 140 deletions
diff --git a/lib/rfq-last/attachment/vendor-response-table.tsx b/lib/rfq-last/attachment/vendor-response-table.tsx index 22f813b3..b09487d6 100644 --- a/lib/rfq-last/attachment/vendor-response-table.tsx +++ b/lib/rfq-last/attachment/vendor-response-table.tsx @@ -590,7 +590,7 @@ export function VendorResponseTable({ ) : ( <> <CheckCircle2 className="h-3 w-3 mr-1" /> - {selectedVendor} 문서 확정 + {selectedVendor} 설계전송 문서 확정 </> )} </Button> diff --git a/lib/rfq-last/quotation-compare-view.tsx b/lib/rfq-last/quotation-compare-view.tsx index 3bb27b55..7a4fd751 100644 --- a/lib/rfq-last/quotation-compare-view.tsx +++ b/lib/rfq-last/quotation-compare-view.tsx @@ -70,7 +70,6 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { const [selectedResponse, setSelectedResponse] = React.useState<VendorResponseVersion | null>(null); const [selectedVendorName, setSelectedVendorName] = React.useState<string>(""); const [selectedContractType, setSelectedContractType] = React.useState<"PO" | "CONTRACT" | "BIDDING" | "">(""); - const [selectionReason, setSelectionReason] = React.useState(""); const [cancelReason, setCancelReason] = React.useState(""); const [isSubmitting, setIsSubmitting] = React.useState(false); const router = useRouter() @@ -323,11 +322,6 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { return; } - if (!selectionReason.trim()) { - toast.error("선정 사유를 입력해주세요."); - return; - } - setIsSubmitting(true); try { const vendor = data.vendors.find(v => v.vendorId === parseInt(selectedVendorId)); @@ -348,8 +342,8 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { vendorCode: vendor.vendorCode, totalAmount: latestResponse.totalAmount, currency: latestResponse.currency, - selectionReason: selectionReason, - priceRank: latestResponse.rank || 0, + selectionReason: "", + priceRank: 0, hasConditionDifferences: latestResponse.conditionDifferences.hasDifferences, criticalDifferences: latestResponse.conditionDifferences.criticalDifferences, }); @@ -357,7 +351,6 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { if (result.success) { toast.success("업체가 성공적으로 선정되었습니다."); setShowSelectionDialog(false); - setSelectionReason(""); router.refresh() } else { throw new Error(result.error || "업체 선정 중 오류가 발생했습니다."); @@ -410,24 +403,25 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { <Button variant="destructive" onClick={() => setShowCancelDialog(true)} + disabled={selectedContractType !== ""} className="gap-2" > <X className="h-4 w-4" /> 선정 취소 </Button> )} - <Button + {/* <Button variant="outline" onClick={() => { setSelectedVendorId(""); setShowSelectionDialog(true); }} - disabled={isSelectionApproved} + disabled={isSelectionApproved || selectedContractType !== ""} className="gap-2" > <RefreshCw className="h-4 w-4" /> 재선정 - </Button> + </Button> */} </> ) : ( <Button @@ -479,7 +473,6 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { <p className="font-semibold">선정 업체: {selectedVendor.vendorName} ({selectedVendor.vendorCode})</p> <p>선정 금액: {formatAmount(selectedVendor.totalAmount, selectedVendor.currency)}</p> <p>선정일: {selectedVendor.selectionDate ? format(new Date(selectedVendor.selectionDate), "yyyy년 MM월 dd일", { locale: ko }) : "-"}</p> - <p>선정 사유: {selectedVendor.selectionReason || "-"}</p> {selectedVendor.contractNo && ( <> <div className="border-t pt-1 mt-2"> @@ -508,6 +501,7 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { setSelectedContractType("PO"); setShowContractDialog(true); }} + disabled={data.rfqInfo.rfqCode?.startsWith("I")} className="gap-1 bg-green-600 hover:bg-green-700 text-xs" > <FileText className="h-3 w-3" /> @@ -929,7 +923,7 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { {/* 납기일 */} <tr> - <td className="p-3 font-medium">납기일</td> + <td className="p-3 font-medium">PR납기 요청일</td> {data.vendors.map((vendor) => { const latestResponse = vendor.responses[0]; return ( @@ -1245,36 +1239,17 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { )} </span> </div> - <div className="flex justify-between"> - <span className="text-sm font-medium">가격 순위</span> - <span className="text-sm"> - #{latestResponse?.rank || 0} - </span> - </div> {latestResponse?.conditionDifferences.hasDifferences && ( <Alert className="mt-2"> <AlertCircle className="h-4 w-4" /> <AlertDescription> - 제시 조건과 차이가 있습니다. 선정 사유를 명확히 기재해주세요. + 제시 조건과 차이가 있습니다. </AlertDescription> </Alert> )} </div> </div> - <div className="space-y-2"> - <label htmlFor="selection-reason" className="text-sm font-medium"> - 선정 사유 * - </label> - <textarea - id="selection-reason" - className="w-full min-h-[100px] p-3 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder="업체 선정 사유를 입력해주세요..." - value={selectionReason} - onChange={(e) => setSelectionReason(e.target.value)} - required - /> - </div> </div> ); })()} @@ -1284,7 +1259,6 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { variant="outline" onClick={() => { setShowSelectionDialog(false); - setSelectionReason(""); }} disabled={isSubmitting} > @@ -1292,7 +1266,7 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { </Button> <Button onClick={handleVendorSelection} - disabled={!selectionReason || isSubmitting} + disabled={isSubmitting} > {isSubmitting ? "처리 중..." : hasSelection ? "재선정 확정" : "선정 확정"} </Button> diff --git a/lib/rfq-last/table/rfq-table-columns.tsx b/lib/rfq-last/table/rfq-table-columns.tsx index e8a5ba94..6976e1c5 100644 --- a/lib/rfq-last/table/rfq-table-columns.tsx +++ b/lib/rfq-last/table/rfq-table-columns.tsx @@ -34,7 +34,7 @@ const getStatusBadgeVariant = (status: string) => { case "RFQ 생성": return "outline"; case "구매담당지정": return "secondary"; case "견적요청문서 확정": return "default"; - case "Short List 확정": return "default"; + case "TBE 요청": return "default"; case "TBE 완료": return "default"; case "RFQ 발송": return "default"; case "견적접수": return "default"; diff --git a/lib/rfq-last/table/rfq-table.tsx b/lib/rfq-last/table/rfq-table.tsx index 46bb4670..80f1422e 100644 --- a/lib/rfq-last/table/rfq-table.tsx +++ b/lib/rfq-last/table/rfq-table.tsx @@ -258,7 +258,7 @@ export function RfqTable({ { label: "RFQ 생성", value: "RFQ 생성" }, { label: "구매담당지정", value: "구매담당지정" }, { label: "견적요청문서 확정", value: "견적요청문서 확정" }, - { label: "Short List 확정", value: "Short List 확정" }, + { label: "TBE 요청", value: "TBE 요청" }, { label: "TBE 완료", value: "TBE 완료" }, { label: "RFQ 발송", value: "RFQ 발송" }, { label: "견적접수", value: "견적접수" }, diff --git a/lib/rfq-last/validations.ts b/lib/rfq-last/validations.ts index 6a5816d4..6b39d52d 100644 --- a/lib/rfq-last/validations.ts +++ b/lib/rfq-last/validations.ts @@ -17,7 +17,7 @@ import { RfqLastAttachments } from "@/db/schema"; { value: "RFQ 생성", label: "RFQ 생성" }, { value: "구매담당지정", label: "구매담당지정" }, { value: "견적요청문서 확정", label: "견적요청문서 확정" }, - { value: "Short List 확정", label: "Short List 확정" }, + { value: "TBE 요청", label: "TBE 요청" }, { value: "TBE 완료", label: "TBE 완료" }, { value: "RFQ 발송", label: "RFQ 발송" }, { value: "견적접수", label: "견적접수" }, diff --git a/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx b/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx index c258293b..6112aed4 100644 --- a/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx +++ b/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx @@ -1096,36 +1096,18 @@ export function BatchUpdateConditionsDialog({ checked={fieldsToUpdate.materialPrice} onCheckedChange={(checked) => { setFieldsToUpdate({ ...fieldsToUpdate, materialPrice: !!checked }); - if (checked) { - form.setValue("materialPriceRelatedYn", true); - } }} /> - <FormField - control={form.control} - name="materialPriceRelatedYn" - render={({ field }) => ( - <FormItem className="flex-1 flex items-center justify-between"> - <div className="space-y-0.5"> - <FormLabel className={cn( - !fieldsToUpdate.materialPrice && "text-muted-foreground" - )}> - 연동제 적용 요건문의 - </FormLabel> - <div className="text-sm text-muted-foreground"> - 원자재 가격 연동 여부 - </div> - </div> - <FormControl> - <Switch - checked={field.value} - onCheckedChange={field.onChange} - disabled={!fieldsToUpdate.materialPrice} - /> - </FormControl> - </FormItem> - )} - /> + <div className="space-y-0.5 flex-1"> + <FormLabel className={cn( + !fieldsToUpdate.materialPrice && "text-muted-foreground" + )}> + 연동제 적용 요건문의 + </FormLabel> + <div className="text-sm text-muted-foreground"> + 원자재 가격 연동 여부 + </div> + </div> </div> {/* Spare Part */} @@ -1140,33 +1122,18 @@ export function BatchUpdateConditionsDialog({ } }} /> - <FormField - control={form.control} - name="sparepartYn" - render={({ field }) => ( - <FormItem className="flex-1 flex items-center justify-between"> - <div className="space-y-0.5"> - <FormLabel className={cn( - !fieldsToUpdate.sparepart && "text-muted-foreground" - )}> - Spare Part - </FormLabel> - <div className="text-sm text-muted-foreground"> - 예비 부품 요구사항 - </div> - </div> - <FormControl> - <Switch - checked={field.value} - onCheckedChange={field.onChange} - disabled={!fieldsToUpdate.sparepart} - /> - </FormControl> - </FormItem> - )} - /> + <div className="space-y-0.5 flex-1"> + <FormLabel className={cn( + !fieldsToUpdate.sparepart && "text-muted-foreground" + )}> + Spare Part + </FormLabel> + <div className="text-sm text-muted-foreground"> + 예비 부품 요구사항 + </div> + </div> </div> - {form.watch("sparepartYn") && fieldsToUpdate.sparepart && ( + {fieldsToUpdate.sparepart && ( <FormField control={form.control} name="sparepartDescription" @@ -1198,33 +1165,18 @@ export function BatchUpdateConditionsDialog({ } }} /> - <FormField - control={form.control} - name="firstYn" - render={({ field }) => ( - <FormItem className="flex-1 flex items-center justify-between"> - <div className="space-y-0.5"> - <FormLabel className={cn( - !fieldsToUpdate.first && "text-muted-foreground" - )}> - 초도품 관리 - </FormLabel> - <div className="text-sm text-muted-foreground"> - 초도품 관리 요구사항 - </div> - </div> - <FormControl> - <Switch - checked={field.value} - onCheckedChange={field.onChange} - disabled={!fieldsToUpdate.first} - /> - </FormControl> - </FormItem> - )} - /> + <div className="space-y-0.5 flex-1"> + <FormLabel className={cn( + !fieldsToUpdate.first && "text-muted-foreground" + )}> + 초도품 관리 + </FormLabel> + <div className="text-sm text-muted-foreground"> + 초도품 관리 요구사항 + </div> + </div> </div> - {form.watch("firstYn") && fieldsToUpdate.first && ( + {fieldsToUpdate.first && ( <FormField control={form.control} name="firstDescription" diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx index 20dc5409..efc17171 100644 --- a/lib/rfq-last/vendor/rfq-vendor-table.tsx +++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx @@ -333,23 +333,44 @@ export function RfqVendorTable({ console.log(mergedData, "mergedData") console.log(rfqId, "rfqId") - // Short List 확정 핸들러 + // TBE 요청 핸들러 const handleShortListConfirm = React.useCallback(async () => { try { setIsUpdatingShortList(true); - // response가 있는 벤더들만 필터링 - const vendorsWithResponse = selectedRows.filter(vendor => - vendor.response && vendor.response.vendor&& vendor.response.isDocumentConfirmed - ); + // response가 있는 벤더들 필터링 + const vendorsWithResponse = selectedRows.filter(vendor => + vendor.response && vendor.response.vendor + ); - if (vendorsWithResponse.length === 0) { - toast.warning("응답이 있는 벤더를 선택해주세요."); - return; - } + if (vendorsWithResponse.length === 0) { + toast.warning("응답이 있는 벤더를 선택해주세요."); + return; + } + + // 문서확정된 벤더들 필터링 + const vendorsWithConfirmedDocs = vendorsWithResponse.filter(vendor => + vendor.response.isDocumentConfirmed + ); + + // 문서확정되지 않은 벤더가 있는 경우 경고 메시지 표시 + const vendorsWithoutConfirmedDocs = vendorsWithResponse.filter(vendor => + !vendor.response.isDocumentConfirmed + ); + + if (vendorsWithoutConfirmedDocs.length > 0) { + toast.warning("벤더회신문서를 확인하시고 설계전송 문서 확정해주세요"); + return; + } + + // 문서확정된 벤더만 TBE 요청 처리 + if (vendorsWithConfirmedDocs.length === 0) { + toast.warning("문서가 확정된 벤더가 없습니다."); + return; + } - const vendorIds = vendorsWithResponse + const vendorIds = vendorsWithConfirmedDocs .map(vendor => vendor.vendorId) .filter(id => id != null); @@ -361,8 +382,8 @@ export function RfqVendorTable({ router.refresh(); } } catch (error) { - console.error("Short List 확정 실패:", error); - toast.error("Short List 확정에 실패했습니다."); + console.error("TBE 요청 실패:", error); + toast.error("TBE 요청에 실패했습니다."); } finally { setIsUpdatingShortList(false); } @@ -1478,7 +1499,7 @@ export function RfqVendorTable({ }, { id: "responseDetail", - header: "회신상세", + header: "제출여부", cell: ({ row }) => { const hasResponse = !!row.original.response?.submission?.submittedAt; @@ -1820,7 +1841,10 @@ export function RfqVendorTable({ // 참여 의사가 있는 선택된 벤더 수 계산 const participatingCount = selectedRows.length; const shortListCount = selectedRows.filter(v => v.shortList).length; - const vendorsWithResponseCount = selectedRows.filter(v => v.response && v.response.vendor && v.response.isDocumentConfirmed).length; + // TBE 요청 버튼용: 응답이 있는 벤더 수 (문서확정 여부와 무관) + const vendorsWithResponseCount = selectedRows.filter(v => v.response && v.response.vendor).length; + // 문서확정된 벤더 수 + const vendorsWithConfirmedDocsCount = selectedRows.filter(v => v.response && v.response.vendor && v.response.isDocumentConfirmed).length; // 견적서가 있는 선택된 벤더 수 계산 (취소되지 않은 벤더만) const quotationCount = nonCancelledRows.filter(row => @@ -1897,7 +1921,7 @@ export function RfqVendorTable({ </Button> )} - {/* Short List 확정 버튼 */} + {/* TBE 요청 버튼 */} {!rfqCode?.startsWith("F") && <Button variant="outline" @@ -1914,7 +1938,7 @@ export function RfqVendorTable({ ) : ( <> <CheckSquare className="h-4 w-4 mr-2" /> - Short List 확정 + TBE 요청 {vendorsWithResponseCount > 0 && ` (${vendorsWithResponseCount})`} </> )} diff --git a/lib/rfq-last/vendor/send-rfq-dialog.tsx b/lib/rfq-last/vendor/send-rfq-dialog.tsx index bf90bc6e..b5dcad5b 100644 --- a/lib/rfq-last/vendor/send-rfq-dialog.tsx +++ b/lib/rfq-last/vendor/send-rfq-dialog.tsx @@ -1059,7 +1059,7 @@ export function SendRfqDialog({ <th className="text-left p-2 text-xs font-medium">No.</th> <th className="text-left p-2 text-xs font-medium">업체명</th> <th className="text-left p-2 text-xs font-medium">기본계약</th> - <th className="text-left p-2 text-xs font-medium"> + {/* <th className="text-left p-2 text-xs font-medium"> <div className="flex items-center gap-2"> <span>계약서 재발송</span> {vendorsWithRecipients.some(v => v.sendVersion && v.sendVersion > 0) && ( @@ -1091,7 +1091,7 @@ export function SendRfqDialog({ </TooltipProvider> )} </div> - </th> + </th> */} <th className="text-left p-2 text-xs font-medium">주 수신자</th> <th className="text-left p-2 text-xs font-medium">CC</th> <th className="text-left p-2 text-xs font-medium">작업</th> @@ -1222,7 +1222,7 @@ export function SendRfqDialog({ <span className="text-xs text-muted-foreground">없음</span> )} </td> - <td className="p-2"> + {/* <td className="p-2"> {isResend && contracts.length > 0 ? ( <div className="flex items-center justify-center"> <TooltipProvider> @@ -1259,7 +1259,7 @@ export function SendRfqDialog({ {isResend ? "계약서 없음" : "-"} </span> )} - </td> + </td> */} <td className="p-2"> <Select value={vendor.selectedMainEmail} |
