summaryrefslogtreecommitdiff
path: root/lib/vendors/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendors/service.ts')
-rw-r--r--lib/vendors/service.ts159
1 files changed, 140 insertions, 19 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[];
}