summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-12-08 03:24:15 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-12-08 03:24:15 +0000
commit198cbcf93b20d3849705dcdbba439326d82b0cae (patch)
treec1afb1c9249048f3f20ad3f478aa2c836b8c9f40
parent4863ba5d336297dddcc8d6d4b414beceb5559742 (diff)
(최겸) 기술영업 rfq 프로젝트 숨기기 기능 추가
-rw-r--r--lib/soap/ecc/mapper/bidding-and-pr-mapper.ts1
-rw-r--r--lib/techsales-rfq/repository.ts1
-rw-r--r--lib/techsales-rfq/service.ts28
-rw-r--r--lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx3
-rw-r--r--lib/techsales-rfq/table/detail-table/vendor-contact-selection-dialog.tsx22
-rw-r--r--lib/techsales-rfq/vendor-response/detail/project-info-tab.tsx13
-rw-r--r--lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx7
7 files changed, 63 insertions, 12 deletions
diff --git a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts
index 9bf61452..4cdaf90d 100644
--- a/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts
+++ b/lib/soap/ecc/mapper/bidding-and-pr-mapper.ts
@@ -296,6 +296,7 @@ export async function mapECCBiddingHeaderToBidding(
// PR 정보
prNumber, // 첫번째 PR의 ZREQ_FN 값
hasPrDocument: false, // PR문서는 POS를 말하는 것으로 보임.
+ plant: eccHeader.WERKS || null, // 플랜트 코드(WERKS)
// 상태 및 설정
status: 'bidding_generated', // 입찰생성 상태
diff --git a/lib/techsales-rfq/repository.ts b/lib/techsales-rfq/repository.ts
index e6138651..61072d3f 100644
--- a/lib/techsales-rfq/repository.ts
+++ b/lib/techsales-rfq/repository.ts
@@ -94,6 +94,7 @@ export async function selectTechSalesRfqsWithJoin(
// 담당자 및 비고
picCode: techSalesRfqs.picCode,
+ hideProjectInfoForVendors: techSalesRfqs.hideProjectInfoForVendors,
remark: techSalesRfqs.remark,
cancelReason: techSalesRfqs.cancelReason,
description: techSalesRfqs.description,
diff --git a/lib/techsales-rfq/service.ts b/lib/techsales-rfq/service.ts
index 13d0bbce..8ce41cba 100644
--- a/lib/techsales-rfq/service.ts
+++ b/lib/techsales-rfq/service.ts
@@ -557,6 +557,7 @@ export async function sendTechSalesRfqToVendors(input: {
email?: string | null;
epId?: string | null;
};
+ hideProjectInfoForVendors?: boolean;
}) {
unstable_noStore();
try {
@@ -573,6 +574,7 @@ export async function sendTechSalesRfqToVendors(input: {
materialCode: true,
description: true,
rfqType: true,
+ hideProjectInfoForVendors: true,
},
with: {
biddingProject: true,
@@ -604,6 +606,23 @@ export async function sendTechSalesRfqToVendors(input: {
}
const isResend = rfq.status === TECH_SALES_RFQ_STATUSES.RFQ_SENT;
+ const effectiveHideProjectInfo =
+ typeof input.hideProjectInfoForVendors === "boolean"
+ ? input.hideProjectInfoForVendors
+ : rfq.hideProjectInfoForVendors ?? false;
+
+ if (
+ typeof input.hideProjectInfoForVendors === "boolean" &&
+ input.hideProjectInfoForVendors !== rfq.hideProjectInfoForVendors
+ ) {
+ await db
+ .update(techSalesRfqs)
+ .set({
+ hideProjectInfoForVendors: input.hideProjectInfoForVendors,
+ updatedAt: new Date(),
+ })
+ .where(eq(techSalesRfqs.id, input.rfqId));
+ }
// 현재 사용자 정보 조회
const sender = await db.query.users.findFirst({
@@ -728,6 +747,9 @@ export async function sendTechSalesRfqToVendors(input: {
const rfqItemsResult = await getTechSalesRfqItems(rfq.id);
const rfqItems = rfqItemsResult.data || [];
+ const projectNameForVendor = effectiveHideProjectInfo ? "" : rfq.biddingProject?.projNm || "";
+ const projectCodeForVendor = effectiveHideProjectInfo ? "" : rfq.biddingProject?.pspid || "";
+
// 이메일 컨텍스트 구성
const emailContext = {
language: language,
@@ -735,8 +757,8 @@ export async function sendTechSalesRfqToVendors(input: {
id: rfq.id,
code: rfq.rfqCode,
title: rfqItems.length > 0 ? rfqItems.map(item => item.itemList).join(', ') : '',
- projectCode: rfq.biddingProject?.pspid || '',
- projectName: rfq.biddingProject?.projNm || '',
+ projectCode: projectCodeForVendor,
+ projectName: projectNameForVendor,
description: rfq.remark || '',
dueDate: rfq.dueDate ? formatDate(rfq.dueDate, "KR") : 'N/A',
materialCode: rfq.materialCode || '',
@@ -990,6 +1012,7 @@ export async function getTechSalesVendorQuotation(quotationId: number) {
projMsrm: quotation.projMsrm,
ptypeNm: quotation.ptypeNm,
} : null,
+ hideProjectInfoForVendors: quotation.hideProjectInfoForVendors ?? false,
},
// 벤더 정보
@@ -1414,6 +1437,7 @@ export async function getVendorQuotations(input: {
dueDate: techSalesRfqs.dueDate,
rfqStatus: techSalesRfqs.status,
description: techSalesRfqs.description,
+ hideProjectInfoForVendors: techSalesRfqs.hideProjectInfoForVendors,
// 프로젝트 정보 (직접 조인)
projNm: biddingProjects.projNm,
// 아이템 개수
diff --git a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
index aee15594..d8ced6f8 100644
--- a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
+++ b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
@@ -257,7 +257,7 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
contactId: number;
contactEmail: string;
contactName: string;
- }>) => {
+ }>, options?: { hideProjectInfoForVendors?: boolean }) => {
if (!selectedRfqId) {
toast.error("선택된 RFQ가 없습니다.");
return;
@@ -301,6 +301,7 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
name: session.data.user.name || undefined,
email: session.data.user.email || undefined,
},
+ hideProjectInfoForVendors: options?.hideProjectInfoForVendors,
});
if (result.success) {
diff --git a/lib/techsales-rfq/table/detail-table/vendor-contact-selection-dialog.tsx b/lib/techsales-rfq/table/detail-table/vendor-contact-selection-dialog.tsx
index d83394bb..8daa9be7 100644
--- a/lib/techsales-rfq/table/detail-table/vendor-contact-selection-dialog.tsx
+++ b/lib/techsales-rfq/table/detail-table/vendor-contact-selection-dialog.tsx
@@ -49,7 +49,10 @@ interface VendorContactSelectionDialogProps {
onOpenChange: (open: boolean) => void
vendorIds: number[]
rfqId?: number // RFQ ID 추가
- onSendRfq: (selectedContacts: SelectedContact[]) => Promise<void>
+ onSendRfq: (
+ selectedContacts: SelectedContact[],
+ options: { hideProjectInfoForVendors: boolean }
+ ) => Promise<void>
}
export function VendorContactSelectionDialog({
@@ -63,6 +66,7 @@ export function VendorContactSelectionDialog({
const [selectedContacts, setSelectedContacts] = useState<SelectedContact[]>([])
const [isLoading, setIsLoading] = useState(false)
const [isSending, setIsSending] = useState(false)
+ const [hideProjectInfoForVendors, setHideProjectInfoForVendors] = useState(false)
// 벤더 contact 정보 조회
useEffect(() => {
@@ -77,6 +81,7 @@ export function VendorContactSelectionDialog({
setVendorsWithContacts({})
setSelectedContacts([])
setIsLoading(false)
+ setHideProjectInfoForVendors(false)
}
}, [open])
@@ -177,7 +182,7 @@ export function VendorContactSelectionDialog({
try {
setIsSending(true)
- await onSendRfq(selectedContacts)
+ await onSendRfq(selectedContacts, { hideProjectInfoForVendors })
onOpenChange(false)
} catch (error) {
console.error("RFQ 발송 오류:", error)
@@ -328,8 +333,17 @@ export function VendorContactSelectionDialog({
<DialogFooter>
<div className="flex items-center justify-between w-full">
- <div className="text-sm text-muted-foreground">
- 총 {selectedContacts.length}명의 연락처가 선택됨
+ <div className="flex flex-col gap-2">
+ <div className="text-sm text-muted-foreground">
+ 총 {selectedContacts.length}명의 연락처가 선택됨
+ </div>
+ <label className="flex items-center gap-2 text-sm">
+ <Checkbox
+ checked={hideProjectInfoForVendors}
+ onCheckedChange={(checked) => setHideProjectInfoForVendors(!!checked)}
+ />
+ 벤더 화면에서 프로젝트명/선주명을 숨기기
+ </label>
</div>
<div className="flex gap-2">
<Button variant="outline" onClick={() => onOpenChange(false)}>
diff --git a/lib/techsales-rfq/vendor-response/detail/project-info-tab.tsx b/lib/techsales-rfq/vendor-response/detail/project-info-tab.tsx
index 8a45f529..31e87330 100644
--- a/lib/techsales-rfq/vendor-response/detail/project-info-tab.tsx
+++ b/lib/techsales-rfq/vendor-response/detail/project-info-tab.tsx
@@ -16,6 +16,7 @@ interface ProjectInfoTabProps {
dueDate: Date | null
status: string | null
remark: string | null
+ hideProjectInfoForVendors?: boolean
biddingProject?: {
id: number
pspid: string | null
@@ -110,7 +111,9 @@ export function ProjectInfoTab({ quotation }: ProjectInfoTabProps) {
<CardHeader>
<CardTitle className="flex items-center gap-2">
프로젝트 기본 정보
- <Badge variant="outline">{rfq.biddingProject.pspid || "N/A"}</Badge>
+ <Badge variant="outline">
+ {rfq.hideProjectInfoForVendors ? "비공개" : (rfq.biddingProject.pspid || "N/A")}
+ </Badge>
</CardTitle>
<CardDescription>
연결된 프로젝트의 기본 정보
@@ -120,11 +123,15 @@ export function ProjectInfoTab({ quotation }: ProjectInfoTabProps) {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<div className="text-sm font-medium text-muted-foreground">프로젝트 ID</div>
- <div className="text-sm">{rfq.biddingProject.pspid || "N/A"}</div>
+ <div className="text-sm">
+ {rfq.hideProjectInfoForVendors ? "비공개" : (rfq.biddingProject.pspid || "N/A")}
+ </div>
</div>
<div className="space-y-2">
<div className="text-sm font-medium text-muted-foreground">프로젝트명</div>
- <div className="text-sm">{rfq.biddingProject.projNm || "N/A"}</div>
+ <div className="text-sm">
+ {rfq.hideProjectInfoForVendors ? "비공개" : (rfq.biddingProject.projNm || "N/A")}
+ </div>
</div>
<div className="space-y-2">
<div className="text-sm font-medium text-muted-foreground">프로젝트 섹터</div>
diff --git a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx
index aabe7a64..97f21be2 100644
--- a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx
+++ b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx
@@ -27,6 +27,7 @@ interface QuotationWithRfqCode extends TechSalesVendorQuotations {
materialCode?: string;
dueDate?: Date;
rfqStatus?: string;
+ hideProjectInfoForVendors?: boolean;
// 아이템 정보
itemName?: string;
@@ -258,17 +259,19 @@ export function getColumns({ router, openAttachmentsSheet, openItemsDialog, open
),
cell: ({ row }) => {
const projNm = row.getValue("projNm") as string;
+ const hideProjectInfo = row.original.hideProjectInfoForVendors === true;
+ const displayValue = hideProjectInfo ? "비공개" : projNm || "N/A";
return (
<div className="min-w-48 max-w-64">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="truncate block text-sm">
- {projNm || "N/A"}
+ {displayValue}
</span>
</TooltipTrigger>
<TooltipContent>
- <p className="max-w-xs">{projNm || "N/A"}</p>
+ <p className="max-w-xs">{displayValue}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>