summaryrefslogtreecommitdiff
path: root/lib/rfq-last
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-11 09:22:58 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-11 09:22:58 +0000
commit535de26b9cf3242c04543d6891d78352b9593a60 (patch)
treecfb2a76a3442ad0ec1d2b2b75293658d93a45808 /lib/rfq-last
parent88b737a71372353e47c466553273d88f5bf36834 (diff)
(최겸) 구매 수정사항 개발
Diffstat (limited to 'lib/rfq-last')
-rw-r--r--lib/rfq-last/attachment/vendor-response-table.tsx2
-rw-r--r--lib/rfq-last/quotation-compare-view.tsx46
-rw-r--r--lib/rfq-last/table/rfq-table-columns.tsx2
-rw-r--r--lib/rfq-last/table/rfq-table.tsx2
-rw-r--r--lib/rfq-last/validations.ts2
-rw-r--r--lib/rfq-last/vendor/batch-update-conditions-dialog.tsx112
-rw-r--r--lib/rfq-last/vendor/rfq-vendor-table.tsx56
-rw-r--r--lib/rfq-last/vendor/send-rfq-dialog.tsx8
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}