diff options
Diffstat (limited to 'lib/vendors/service.ts')
| -rw-r--r-- | lib/vendors/service.ts | 173 |
1 files changed, 86 insertions, 87 deletions
diff --git a/lib/vendors/service.ts b/lib/vendors/service.ts index 853b3701..e3309786 100644 --- a/lib/vendors/service.ts +++ b/lib/vendors/service.ts @@ -1654,14 +1654,20 @@ export async function generatePQNumber(isProject: boolean = false) { } } -export async function requestPQVendors(input: ApproveVendorsInput & { userId: number }) { +export async function requestPQVendors(input: ApproveVendorsInput & { + userId: number, + agreements?: Record<string, boolean>, + dueDate?: string | null, + type?: "GENERAL" | "PROJECT" | "NON_INSPECTION", + extraNote?: string, + pqItems?: string +}) { unstable_noStore(); - + const session = await getServerSession(authOptions); const requesterId = session?.user?.id ? Number(session.user.id) : null; try { - // 프로젝트 정보 가져오기 (projectId가 있는 경우) let projectInfo = null; if (input.projectId) { const project = await db @@ -1673,95 +1679,72 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu .from(projects) .where(eq(projects.id, input.projectId)) .limit(1); - + if (project.length > 0) { projectInfo = project[0]; } } - - // 트랜잭션 내에서 협력업체 상태 업데이트 및 이메일 발송 + const result = await db.transaction(async (tx) => { - // 0. 업데이트 전 협력업체 상태 조회 const vendorsBeforeUpdate = await tx - .select({ - id: vendors.id, - status: vendors.status, - }) + .select({ id: vendors.id, status: vendors.status }) .from(vendors) .where(inArray(vendors.id, input.ids)); - - // 1. 협력업체 상태 업데이트 + const [updated] = await tx .update(vendors) - .set({ - status: "IN_PQ", - updatedAt: new Date() - }) + .set({ status: "IN_PQ", updatedAt: new Date() }) .where(inArray(vendors.id, input.ids)) .returning(); - - // 2. 업데이트된 협력업체 정보 조회 + const updatedVendors = await tx - .select({ - id: vendors.id, - vendorName: vendors.vendorName, - email: vendors.email, - }) + .select({ id: vendors.id, vendorName: vendors.vendorName, email: vendors.email }) .from(vendors) .where(inArray(vendors.id, input.ids)); - - // 3. vendorPQSubmissions 테이블에 레코드 추가 (프로젝트 PQ와 일반 PQ 모두) - const pqType = input.projectId ? "PROJECT" : "GENERAL"; + + const pqType = input.type; const currentDate = new Date(); - - // 기존 PQ 요청이 있는지 확인 (중복 방지) + const existingSubmissions = await tx - .select({ - vendorId: vendorPQSubmissions.vendorId, - projectId: vendorPQSubmissions.projectId, - type: vendorPQSubmissions.type - }) + .select({ vendorId: vendorPQSubmissions.vendorId }) .from(vendorPQSubmissions) .where( and( inArray(vendorPQSubmissions.vendorId, input.ids), - eq(vendorPQSubmissions.type, pqType), - input.projectId + pqType ? eq(vendorPQSubmissions.type, pqType) : undefined, + input.projectId ? eq(vendorPQSubmissions.projectId, input.projectId) : isNull(vendorPQSubmissions.projectId) ) ); - - // 중복되지 않는 벤더에 대해서만 새 PQ 요청 생성 - const existingVendorIds = new Set(existingSubmissions.map(s => s.vendorId)); - const newVendorIds = input.ids.filter(id => !existingVendorIds.has(id)); - + + const existingVendorIds = new Set(existingSubmissions.map((s) => s.vendorId)); + const newVendorIds = input.ids.filter((id) => !existingVendorIds.has(id)); + if (newVendorIds.length > 0) { - // 각 벤더별로 유니크한 PQ 번호 생성 및 저장 const vendorPQDataPromises = newVendorIds.map(async (vendorId) => { - // PQ 번호 생성 (프로젝트 PQ인지 여부 전달) const pqNumber = await generatePQNumber(pqType === "PROJECT"); - - return { - vendorId, - pqNumber, // 생성된 PQ 번호 저장 - projectId: input.projectId || null, - type: pqType, - status: "REQUESTED", - requesterId: input.userId || requesterId, // 요청자 ID 저장 - createdAt: currentDate, - updatedAt: currentDate, - }; + + return { + vendorId, + pqNumber, + projectId: input.projectId || null, + type: pqType, + status: "REQUESTED", + requesterId: input.userId || requesterId, + dueDate: input.dueDate ? new Date(input.dueDate) : null, + agreements: input.agreements ?? {}, + pqItems: input.pqItems || null, + createdAt: currentDate, + updatedAt: currentDate, + }; }); - - // 모든 PQ 번호 생성 완료 대기 + const vendorPQData = await Promise.all(vendorPQDataPromises); - - // 트랜잭션 내에서 데이터 삽입 + await tx.insert(vendorPQSubmissions).values(vendorPQData); } - // 4. 로그 기록 await Promise.all( vendorsBeforeUpdate.map(async (vendorBefore) => { await tx.insert(vendorsLogs).values({ @@ -1770,25 +1753,23 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu action: "status_change", oldStatus: vendorBefore.status, newStatus: "IN_PQ", - comment: input.projectId - ? `Project PQ requested (Project: ${projectInfo?.projectCode || input.projectId})` + comment: input.projectId + ? `Project PQ requested (Project: ${projectInfo?.projectCode || input.projectId})` : "General PQ requested", }); }) ); const headersList = await headers(); - const host = headersList.get('host') || 'localhost:3000'; + const host = headersList.get("host") || "localhost:3000"; - // 5. 각 벤더에게 이메일 발송 await Promise.all( updatedVendors.map(async (vendor) => { - if (!vendor.email) return; // 이메일이 없으면 스킵 - + if (!vendor.email) return; + try { - const userLang = "en"; // 기본값, 필요시 협력업체 언어 설정에서 가져오기 - - // PQ 번호 조회 (이메일에 포함하기 위해) + const userLang = "en"; + const vendorPQ = await tx .select({ pqNumber: vendorPQSubmissions.pqNumber }) .from(vendorPQSubmissions) @@ -1796,59 +1777,76 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu and( eq(vendorPQSubmissions.vendorId, vendor.id), eq(vendorPQSubmissions.type, pqType), - input.projectId + input.projectId ? eq(vendorPQSubmissions.projectId, input.projectId) : isNull(vendorPQSubmissions.projectId) ) ) .limit(1) - .then(rows => rows[0]); - - // 프로젝트 PQ인지 일반 PQ인지에 따라 제목 변경 + .then((rows) => rows[0]); + const subject = input.projectId - ? `[eVCP] You are invited to submit Project PQ ${vendorPQ?.pqNumber || ''} for ${projectInfo?.projectCode || 'a project'}` - : `[eVCP] You are invited to submit PQ ${vendorPQ?.pqNumber || ''}`; - - // 로그인 URL에 프로젝트 ID 추가 (프로젝트 PQ인 경우) + ? `[eVCP] You are invited to submit Project PQ ${vendorPQ?.pqNumber || ""} for ${projectInfo?.projectCode || "a project"}` + : input.type === "NON_INSPECTION" + ? `[eVCP] You are invited to submit Non-Inspection PQ ${vendorPQ?.pqNumber || ""}` + : `[eVCP] You are invited to submit PQ ${vendorPQ?.pqNumber || ""}`; + const baseLoginUrl = `${host}/partners/pq`; const loginUrl = input.projectId ? `${baseLoginUrl}?projectId=${input.projectId}` : baseLoginUrl; - + + // 체크된 계약 항목 배열 생성 + const contracts = input.agreements + ? Object.entries(input.agreements) + .filter(([_, checked]) => checked) + .map(([name, _]) => name) + : []; + + // PQ 대상 품목 + const pqItems = input.pqItems || " - "; + await sendEmail({ to: vendor.email, subject, - template: input.projectId ? "project-pq" : "pq", // 프로젝트별 템플릿 사용 + template: input.projectId ? "project-pq" : input.type === "NON_INSPECTION" ? "non-inspection-pq" : "pq", context: { vendorName: vendor.vendorName, + vendorContact: "", // 담당자 정보가 없으므로 빈 문자열 + pqNumber: vendorPQ?.pqNumber || "", + senderName: session?.user?.name || "eVCP", + senderEmail: session?.user?.email || "noreply@evcp.com", + dueDate: input.dueDate ? new Date(input.dueDate).toLocaleDateString('ko-KR') : "", + pqItems, + contracts, + extraNote: input.extraNote || "", + currentYear: new Date().getFullYear().toString(), loginUrl, language: userLang, - projectCode: projectInfo?.projectCode || '', - projectName: projectInfo?.projectName || '', + projectCode: projectInfo?.projectCode || "", + projectName: projectInfo?.projectName || "", hasProject: !!input.projectId, - pqNumber: vendorPQ?.pqNumber || '', // PQ 번호 추가 + pqType: input.type || "GENERAL", }, }); } catch (emailError) { console.error(`Failed to send email to vendor ${vendor.id}:`, emailError); - // 이메일 전송 실패는 전체 트랜잭션을 실패시키지 않음 } }) ); - + return updated; }); - - // 캐시 무효화 + revalidateTag("vendors"); revalidateTag("vendor-status-counts"); revalidateTag("vendor-pq-submissions"); - + if (input.projectId) { revalidateTag(`project-${input.projectId}`); revalidateTag(`project-pq-submissions-${input.projectId}`); } - + return { data: result, error: null }; } catch (err) { console.error("Error requesting PQ from vendors:", err); @@ -1856,6 +1854,7 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu } } + interface SendVendorsInput { ids: number[]; } |
