diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 09:43:03 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 09:43:03 +0000 |
| commit | b99e57a028703c8f3d9526c47bc51774490f4546 (patch) | |
| tree | 732d5dc1130d163c59a7f31dbcc81fe2ca86b8c2 /lib/rfq-last/service.ts | |
| parent | fd542b5ad4bf94b82d872f87b96aa2e7514ffbc3 (diff) | |
(대표님) 구매 RFQ AVL dialog 추가
Diffstat (limited to 'lib/rfq-last/service.ts')
| -rw-r--r-- | lib/rfq-last/service.ts | 269 |
1 files changed, 267 insertions, 2 deletions
diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts index 43943c71..98d0e750 100644 --- a/lib/rfq-last/service.ts +++ b/lib/rfq-last/service.ts @@ -3,7 +3,7 @@ import { revalidatePath, unstable_cache, unstable_noStore } from "next/cache"; import db from "@/db/db"; -import { paymentTerms, incoterms, rfqLastVendorQuotationItems, rfqLastVendorAttachments, rfqLastVendorResponses, RfqsLastView, rfqLastAttachmentRevisions, rfqLastAttachments, rfqsLast, rfqsLastView, users, rfqPrItems, prItemsLastView, vendors, rfqLastDetails, rfqLastVendorResponseHistory, rfqLastDetailsView, vendorContacts, projects, basicContract, basicContractTemplates, rfqLastTbeSessions, rfqLastTbeDocumentReviews } from "@/db/schema"; +import { avlVendorInfo, paymentTerms, incoterms, rfqLastVendorQuotationItems, rfqLastVendorAttachments, rfqLastVendorResponses, RfqsLastView, rfqLastAttachmentRevisions, rfqLastAttachments, rfqsLast, rfqsLastView, users, rfqPrItems, prItemsLastView, vendors, rfqLastDetails, rfqLastVendorResponseHistory, rfqLastDetailsView, vendorContacts, projects, basicContract, basicContractTemplates, rfqLastTbeSessions, rfqLastTbeDocumentReviews } from "@/db/schema"; import { sql, and, desc, asc, like, ilike, or, eq, SQL, count, gte, lte, isNotNull, ne, inArray } from "drizzle-orm"; import { filterColumns } from "@/lib/filter-columns"; import { GetRfqLastAttachmentsSchema, GetRfqsSchema } from "./validations"; @@ -4429,4 +4429,269 @@ export async function assignPicToRfqs({ rfqIds, picUserId }: AssignPicParams) { message: error instanceof Error ? error.message : "담당자 지정 중 오류가 발생했습니다." }; } -}
\ No newline at end of file +} + + +// AVL 벤더 정보 가져오기 +export async function getAvlVendorsForRfq(rfqId: number) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + throw new Error("인증이 필요합니다."); + } + + // 1. RFQ 정보 조회하여 프로젝트 코드 가져오기 + const rfqData = await db.select({ + projectId: rfqsLast.projectId, + projectCode: projects.projectCode, + }) + .from(rfqsLast) + .leftJoin(projects, eq(rfqsLast.projectId, projects.id)) + .where(eq(rfqsLast.id, rfqId)) + .limit(1); + + if (!rfqData[0]?.projectCode) { + return { + success: false, + error: "RFQ에 연결된 프로젝트 코드를 찾을 수 없습니다." + }; + } + + const projectCode = rfqData[0].projectCode; + + // 2. RFQ PR Items에서 major인 자재그룹 코드 가져오기 + const majorMaterials = await db.select({ + materialCategory: rfqPrItems.materialCategory, + }) + .from(rfqPrItems) + .where( + and( + eq(rfqPrItems.rfqsLastId, rfqId), + isTrue(rfqPrItems.majorYn) + ) + ) + .groupBy(rfqPrItems.materialCategory); + + if (majorMaterials.length === 0) { + return { + success: false, + error: "Major 자재그룹을 찾을 수 없습니다." + }; + } + + const materialGroupCodes = majorMaterials + .map(m => m.materialCategory) + .filter(Boolean); + + // 3. AVL 벤더 정보 조회 + const avlVendors = await db.select({ + id: avlVendorInfo.id, + vendorId: avlVendorInfo.vendorId, + vendorName: avlVendorInfo.vendorName, + vendorCode: avlVendorInfo.vendorCode, + avlVendorName: avlVendorInfo.avlVendorName, + tier: avlVendorInfo.tier, + headquarterLocation: avlVendorInfo.headquarterLocation, + manufacturingLocation: avlVendorInfo.manufacturingLocation, + materialGroupCode: avlVendorInfo.materialGroupCode, + materialGroupName: avlVendorInfo.materialGroupName, + packageName: avlVendorInfo.packageName, + isAgent: avlVendorInfo.isAgent, + hasAvl: avlVendorInfo.hasAvl, + isBlacklist: avlVendorInfo.isBlacklist, + isBcc: avlVendorInfo.isBcc, + remark: avlVendorInfo.remark, + }) + .from(avlVendorInfo) + .where( + and( + eq(avlVendorInfo.projectCode, projectCode), + // materialGroupCode가 materialGroupCodes 중 하나와 일치 + ...(materialGroupCodes.length > 0 + ? [sql`${avlVendorInfo.materialGroupCode} = ANY(${materialGroupCodes})`] + : [] + ) + ) + ); + + // 4. 이미 RFQ에 추가된 벤더 ID 조회 + const existingVendors = await db.select({ + vendorId: rfqLastDetails.vendorsId, + }) + .from(rfqLastDetails) + .where( + and( + eq(rfqLastDetails.rfqsLastId, rfqId), + isTrue(rfqLastDetails.isLatest) + ) + ); + + const existingVendorIds = existingVendors + .map(v => v.vendorId) + .filter(Boolean); + + // 5. 벤더 정보가 없는 AVL 레코드에 대해 실제 벤더 정보 조회 및 매칭 + const vendorsWithoutId = avlVendors.filter(v => !v.vendorId); + if (vendorsWithoutId.length > 0) { + // 벤더 이름과 코드로 실제 벤더 찾기 + const vendorNames = vendorsWithoutId.map(v => v.vendorName).filter(Boolean); + const vendorCodes = vendorsWithoutId.map(v => v.vendorCode).filter(Boolean); + + const actualVendors = await db.select({ + id: vendors.id, + vendorName: vendors.vendorName, + vendorCode: vendors.vendorCode, + }) + .from(vendors) + .where( + or( + ...(vendorNames.length > 0 ? [sql`${vendors.vendorName} = ANY(${vendorNames})`] : []), + ...(vendorCodes.length > 0 ? [sql`${vendors.vendorCode} = ANY(${vendorCodes})`] : []) + ) + ); + + // AVL 레코드에 실제 벤더 ID 매칭 + avlVendors.forEach(avlVendor => { + if (!avlVendor.vendorId) { + const matchedVendor = actualVendors.find(v => + v.vendorName === avlVendor.vendorName || + v.vendorCode === avlVendor.vendorCode + ); + if (matchedVendor) { + avlVendor.vendorId = matchedVendor.id; + } + } + }); + } + + return { + success: true, + vendors: avlVendors, + existingVendorIds, + projectCode, + materialGroupCodes, + }; + + } catch (error) { + console.error("AVL 벤더 조회 오류:", error); + return { + success: false, + error: "AVL 벤더 정보를 가져오는 중 오류가 발생했습니다." + }; + } +} + +// AVL 벤더를 RFQ에 추가 +export async function addAvlVendorsToRfq({ + rfqId, + vendors, +}: { + rfqId: number; + vendors: Array<{ + vendorId: number; + vendorName: string; + vendorCode: string | null; + contractRequirements: { + agreementYn: boolean; + ndaYn: boolean; + gtcType: "general" | "project" | "none"; + }; + }>; +}) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + throw new Error("인증이 필요합니다."); + } + const userId = Number(session.user.id); + + // 이미 추가된 벤더 확인 + const existingDetails = await db.select({ + vendorId: rfqLastDetails.vendorsId, + }) + .from(rfqLastDetails) + .where( + and( + eq(rfqLastDetails.rfqsLastId, rfqId), + isTrue(rfqLastDetails.isLatest) + ) + ); + + const existingVendorIds = new Set( + existingDetails.map(d => d.vendorId).filter(Boolean) + ); + + // 추가할 벤더 필터링 + const vendorsToAdd = vendors.filter(v => !existingVendorIds.has(v.vendorId)); + + if (vendorsToAdd.length === 0) { + return { + success: true, + addedCount: 0, + skippedCount: vendors.length, + message: "모든 벤더가 이미 추가되어 있습니다." + }; + } + + // 벤더 추가 + const newDetails = await Promise.all( + vendorsToAdd.map(async (vendor) => { + const { contractRequirements } = vendor; + + // 벤더 정보 조회하여 위치 확인 + const vendorInfo = await db.select({ + country: vendors.country, + }) + .from(vendors) + .where(eq(vendors.id, vendor.vendorId)) + .limit(1); + + const isInternational = vendorInfo[0]?.country && + vendorInfo[0].country !== "KR" && + vendorInfo[0].country !== "한국"; + + return db.insert(rfqLastDetails).values({ + rfqsLastId: rfqId, + vendorsId: vendor.vendorId, + + // 기본계약 설정 + agreementYn: contractRequirements.agreementYn, + ndaYn: contractRequirements.ndaYn, + generalGtcYn: isInternational && contractRequirements.gtcType === "general", + projectGtcYn: isInternational && contractRequirements.gtcType === "project", + gtcType: isInternational ? contractRequirements.gtcType : "none", + + // 기본값 설정 + currency: "USD", + shortList: false, + returnYn: false, + materialPriceRelatedYn: false, + sparepartYn: false, + firstYn: false, + sendVersion: 0, + isLatest: true, + + createdAt: new Date(), + createdBy: userId, + updatedAt: new Date(), + updatedBy: userId, + }).returning(); + }) + ); + + return { + success: true, + addedCount: newDetails.length, + skippedCount: vendors.length - newDetails.length, + message: `${newDetails.length}개의 AVL 벤더가 추가되었습니다.`, + addedVendors: newDetails, + }; + + } catch (error) { + console.error("AVL 벤더 추가 오류:", error); + return { + success: false, + error: "AVL 벤더 추가 중 오류가 발생했습니다." + }; + } +} |
