diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-28 00:32:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-28 00:32:31 +0000 |
| commit | 20800b214145ee6056f94ca18fa1054f145eb977 (patch) | |
| tree | b5c8b27febe5b126e6d9ece115ea05eace33a020 /lib/vendors | |
| parent | e1344a5da1aeef8fbf0f33e1dfd553078c064ccc (diff) | |
(대표님) lib 파트 커밋
Diffstat (limited to 'lib/vendors')
| -rw-r--r-- | lib/vendors/service.ts | 159 | ||||
| -rw-r--r-- | lib/vendors/table/vendors-table-toolbar-actions.tsx | 8 |
2 files changed, 141 insertions, 26 deletions
diff --git a/lib/vendors/service.ts b/lib/vendors/service.ts index 87a8336d..c9ee55be 100644 --- a/lib/vendors/service.ts +++ b/lib/vendors/service.ts @@ -2,7 +2,7 @@ import { revalidateTag, unstable_noStore } from "next/cache"; import db from "@/db/db"; -import { vendorAttachments, VendorContact, vendorContacts, vendorDetailView, vendorInvestigations, vendorInvestigationsView, vendorItemsView, vendorPossibleItems, vendors, vendorsWithTypesView, vendorTypes, type Vendor } from "@/db/schema/vendors"; +import { vendorAttachments, VendorContact, vendorContacts, vendorDetailView, vendorItemsView, vendorPossibleItems, vendors, vendorsWithTypesView, vendorTypes, type Vendor } from "@/db/schema"; import logger from '@/lib/logger'; import { filterColumns } from "@/lib/filter-columns"; @@ -42,7 +42,7 @@ import type { GetRfqHistorySchema, } from "./validations"; -import { asc, desc, ilike, inArray, and, or, gte, lte, eq, isNull, count } from "drizzle-orm"; +import { asc, desc, ilike, inArray, and, or, gte, lte, eq, isNull, count, sql } from "drizzle-orm"; import { rfqItems, rfqs, vendorRfqView } from "@/db/schema/rfq"; import path from "path"; import fs from "fs/promises"; @@ -55,7 +55,7 @@ import { items } from "@/db/schema/items"; import { users } from "@/db/schema/users"; import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; -import { contracts, contractsDetailView, projects, vendorProjectPQs, vendorsLogs } from "@/db/schema"; +import { contracts, contractsDetailView, projects, vendorPQSubmissions, vendorProjectPQs, vendorsLogs } from "@/db/schema"; import { Hospital } from "lucide-react"; @@ -154,6 +154,8 @@ export async function getVendors(input: GetVendorsSchema) { const total = await countVendorsWithTypes(tx, where); return { data: vendorsWithAttachments, total }; }); + + console.log(total) // 페이지 수 const pageCount = Math.ceil(total / input.perPage); @@ -1181,9 +1183,71 @@ export async function approveVendors(input: ApproveVendorsInput & { userId: numb } } +/** + * 유니크한 PQ 번호 생성 함수 + * + * 형식: PQ-YYMMDD-XXXXX + * YYMMDD: 연도(YY), 월(MM), 일(DD) + * XXXXX: 시퀀스 번호 (00001부터 시작) + * + * 예: PQ-240520-00001, PQ-240520-00002, ... + */ +export async function generatePQNumber(isProject: boolean = false) { + try { + // 현재 날짜 가져오기 + const now = new Date(); + const year = now.getFullYear().toString().slice(-2); // 년도의 마지막 2자리 + const month = (now.getMonth() + 1).toString().padStart(2, '0'); // 월 (01-12) + const day = now.getDate().toString().padStart(2, '0'); // 일 (01-31) + const dateStr = `${year}${month}${day}`; + + // 접두사 설정 (일반 PQ vs 프로젝트 PQ) + const prefix = isProject ? "PPQ" : "PQ"; + const datePrefix = `${prefix}-${dateStr}`; + + // 오늘 생성된 가장 큰 시퀀스 번호 조회 + const latestPQ = await db + .select({ pqNumber: vendorPQSubmissions.pqNumber }) + .from(vendorPQSubmissions) + .where( + sql`${vendorPQSubmissions.pqNumber} LIKE ${datePrefix + '-%'}` + ) + .orderBy(desc(vendorPQSubmissions.pqNumber)) + .limit(1); + + let sequenceNumber = 1; // 기본값은 1 + + // 오늘 생성된 PQ가 있으면 다음 시퀀스 번호 계산 + if (latestPQ.length > 0 && latestPQ[0].pqNumber) { + const lastPQ = latestPQ[0].pqNumber; + const lastSequence = lastPQ.split('-')[2]; + if (lastSequence && !isNaN(parseInt(lastSequence))) { + sequenceNumber = parseInt(lastSequence) + 1; + } + } + + // 5자리 시퀀스 번호로 포맷팅 (00001, 00002, ...) + const formattedSequence = sequenceNumber.toString().padStart(5, '0'); + + // 최종 PQ 번호 생성 + const pqNumber = `${datePrefix}-${formattedSequence}`; + + return pqNumber; + } catch (error) { + console.error('Error generating PQ number:', error); + // 문제 발생 시 대체 번호 생성 (타임스탬프 기반) + const timestamp = Date.now().toString(); + const prefix = isProject ? "PPQ" : "PQ"; + return `${prefix}-${timestamp}`; + } +} + export async function requestPQVendors(input: ApproveVendorsInput & { userId: number }) { unstable_noStore(); + const session = await getServerSession(authOptions); + const requesterId = session?.user?.id ? Number(session.user.id) : null; + try { // 프로젝트 정보 가져오기 (projectId가 있는 경우) let projectInfo = null; @@ -1234,18 +1298,55 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu .from(vendors) .where(inArray(vendors.id, input.ids)); - // 3. 프로젝트 PQ인 경우, vendorProjectPQs 테이블에 레코드 추가 - if (input.projectId && projectInfo) { - // 각 벤더에 대해 프로젝트 PQ 연결 생성 - const vendorProjectPQsData = input.ids.map(vendorId => ({ - vendorId, - projectId: input.projectId!, - status: "REQUESTED", - createdAt: new Date(), - updatedAt: new Date(), - })); + // 3. vendorPQSubmissions 테이블에 레코드 추가 (프로젝트 PQ와 일반 PQ 모두) + const pqType = input.projectId ? "PROJECT" : "GENERAL"; + const currentDate = new Date(); + + // 기존 PQ 요청이 있는지 확인 (중복 방지) + const existingSubmissions = await tx + .select({ + vendorId: vendorPQSubmissions.vendorId, + projectId: vendorPQSubmissions.projectId, + type: vendorPQSubmissions.type + }) + .from(vendorPQSubmissions) + .where( + and( + inArray(vendorPQSubmissions.vendorId, input.ids), + eq(vendorPQSubmissions.type, pqType), + 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)); + + 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, + }; + }); - await tx.insert(vendorProjectPQs).values(vendorProjectPQsData); + // 모든 PQ 번호 생성 완료 대기 + const vendorPQData = await Promise.all(vendorPQDataPromises); + + // 트랜잭션 내에서 데이터 삽입 + await tx.insert(vendorPQSubmissions).values(vendorPQData); } // 4. 로그 기록 @@ -1259,7 +1360,7 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu newStatus: "IN_PQ", comment: input.projectId ? `Project PQ requested (Project: ${projectInfo?.projectCode || input.projectId})` - : "PQ requested", + : "General PQ requested", }); }) ); @@ -1275,10 +1376,26 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu try { const userLang = "en"; // 기본값, 필요시 협력업체 언어 설정에서 가져오기 + // PQ 번호 조회 (이메일에 포함하기 위해) + const vendorPQ = await tx + .select({ pqNumber: vendorPQSubmissions.pqNumber }) + .from(vendorPQSubmissions) + .where( + and( + eq(vendorPQSubmissions.vendorId, vendor.id), + eq(vendorPQSubmissions.type, pqType), + input.projectId + ? eq(vendorPQSubmissions.projectId, input.projectId) + : isNull(vendorPQSubmissions.projectId) + ) + ) + .limit(1) + .then(rows => rows[0]); + // 프로젝트 PQ인지 일반 PQ인지에 따라 제목 변경 const subject = input.projectId - ? `[eVCP] You are invited to submit Project PQ for ${projectInfo?.projectCode || 'a project'}` - : "[eVCP] You are invited to submit PQ"; + ? `[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인 경우) const baseLoginUrl = `${host}/partners/pq`; @@ -1289,8 +1406,7 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu await sendEmail({ to: vendor.email, subject, - template:input.projectId ? "project-pq" : "pq", // 프로젝트별 템플릿 사용 - // template: "vendor-pq-status", // 프로젝트별 템플릿 사용 + template: input.projectId ? "project-pq" : "pq", // 프로젝트별 템플릿 사용 context: { vendorName: vendor.vendorName, loginUrl, @@ -1298,6 +1414,7 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu projectCode: projectInfo?.projectCode || '', projectName: projectInfo?.projectName || '', hasProject: !!input.projectId, + pqNumber: vendorPQ?.pqNumber || '', // PQ 번호 추가 }, }); } catch (emailError) { @@ -1313,8 +1430,11 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu // 캐시 무효화 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 }; @@ -1323,6 +1443,7 @@ export async function requestPQVendors(input: ApproveVendorsInput & { userId: nu return { data: null, error: getErrorMessage(err) }; } } + interface SendVendorsInput { ids: number[]; } diff --git a/lib/vendors/table/vendors-table-toolbar-actions.tsx b/lib/vendors/table/vendors-table-toolbar-actions.tsx index 1c788911..12f1dfcd 100644 --- a/lib/vendors/table/vendors-table-toolbar-actions.tsx +++ b/lib/vendors/table/vendors-table-toolbar-actions.tsx @@ -182,13 +182,7 @@ export function VendorsTableToolbarActions({ table }: VendorsTableToolbarActions /> )} - {pqApprovedVendors.length > 0 && ( - <RequestVendorsInvestigateDialog - vendors={pqApprovedVendors} - onSuccess={() => table.toggleAllRowsSelected(false)} - /> - )} - + {/* Export 드롭다운 메뉴로 변경 */} <DropdownMenu> <DropdownMenuTrigger asChild> |
