diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-30 10:44:47 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-30 10:44:47 +0000 |
| commit | 871a6d46a769cbe9e87146434f4bcb2d6792ab81 (patch) | |
| tree | acc25b3645e2253625e68f3721a203131ff4f3c4 /lib/pq/service.ts | |
| parent | 17b9d2016be7c0ab6571de6aba36b3f4ea37bdb1 (diff) | |
(최겸) 구매 PQ/실사 재개발(테스트 필요), 정규업체등록 결재 개발, 실사 의뢰 결재 후처리 등
Diffstat (limited to 'lib/pq/service.ts')
| -rw-r--r-- | lib/pq/service.ts | 266 |
1 files changed, 200 insertions, 66 deletions
diff --git a/lib/pq/service.ts b/lib/pq/service.ts index 8b1986ce..0bc575a6 100644 --- a/lib/pq/service.ts +++ b/lib/pq/service.ts @@ -555,6 +555,28 @@ export async function submitPQAction({ const targetSubmissionId = existingSubmission?.id || '';
const targetRequesterId = existingSubmission?.requesterId || null;
+ // QM 담당자 이메일 조회 (해당 PQ와 연결된 실사에 배정된 경우)
+ let qmManagerEmail: string | null = null;
+ if (targetSubmissionId) {
+ try {
+ const inv = await db
+ .select({ qmManagerId: vendorInvestigations.qmManagerId })
+ .from(vendorInvestigations)
+ .where(eq(vendorInvestigations.pqSubmissionId, Number(targetSubmissionId)))
+ .then(rows => rows[0]);
+ if (inv?.qmManagerId) {
+ const qmUser = await db
+ .select({ email: users.email })
+ .from(users)
+ .where(eq(users.id, inv.qmManagerId))
+ .then(rows => rows[0]);
+ qmManagerEmail = qmUser?.email || null;
+ }
+ } catch (e) {
+ console.warn("Failed to fetch QM manager email for PQ submission", e);
+ }
+ }
+
if (targetRequesterId !== null) {
try {
// 요청자 정보 조회
@@ -577,6 +599,7 @@ export async function submitPQAction({ await sendEmail({
to: requester.email,
+ cc: qmManagerEmail ? [qmManagerEmail] : undefined,
subject: emailSubject,
template: "pq-submitted-admin",
context: {
@@ -1238,7 +1261,7 @@ export async function requestPqChangesAction({ await db
.update(vendorPQSubmissions)
.set({
- status: "SUBMITTED", // 변경 요청 상태로 설정
+ status: "IN_PROGRESS", // 변경 요청 상태로 설정
updatedAt: new Date(),
})
.where(
@@ -2209,53 +2232,54 @@ export async function approvePQAction({ projectName = projectData?.name || 'Unknown Project';
}
-
- // 5. PQ 상태를 QM_REVIEWING으로 업데이트 (TO-BE: QM 검토 단계 추가)
+
+ // 5. PQ 상태 업데이트
await db
- .update(vendorPQSubmissions)
+ .update(vendorPQSubmissions)
+ .set({
+ status: "APPROVED",
+ approvedAt: currentDate,
+ updatedAt: currentDate,
+ })
+ .where(eq(vendorPQSubmissions.id, pqSubmissionId));
+
+ // 6. 일반 PQ인 경우 벤더 상태 업데이트 (선택사항)
+ if (pqSubmission.type === "GENERAL") {
+ await db
+ .update(vendors)
.set({
- status: "QM_REVIEWING",
+ status: "PQ_APPROVED",
updatedAt: currentDate,
})
- .where(eq(vendorPQSubmissions.id, pqSubmissionId));
-
- // 6. 일반 PQ인 경우 벤더 상태를 IN_PQ로 업데이트 (QM 검토 중)
- if (pqSubmission.type === "GENERAL") {
- await db
- .update(vendors)
- .set({
- status: "IN_PQ",
- updatedAt: currentDate,
- })
- .where(eq(vendors.id, vendorId));
+ .where(eq(vendors.id, vendorId));
}
-
+
// 7. 벤더에게 이메일 알림 발송
if (vendor.email) {
- try {
- const emailSubject = pqSubmission.projectId
- ? `[eVCP] Project PQ Under QM Review for ${projectName}`
- : "[eVCP] General PQ Under QM Review";
-
- const portalUrl = `${host}/partners/pq`;
-
- await sendEmail({
- to: vendor.email,
- subject: emailSubject,
- template: "pq-qm-review-vendor",
- context: {
- vendorName: vendor.vendorName,
- projectId: pqSubmission.projectId,
- projectName: projectName,
- isProjectPQ: !!pqSubmission.projectId,
- reviewDate: currentDate.toLocaleString(),
- portalUrl,
- }
- });
- } catch (emailError) {
- console.error("Failed to send vendor notification:", emailError);
- // 이메일 발송 실패가 전체 프로세스를 중단하지 않음
- }
+ try {
+ const emailSubject = pqSubmission.projectId
+ ? `[eVCP] Project PQ Approved for ${projectName}`
+ : "[eVCP] General PQ Approved";
+
+ const portalUrl = `${host}/partners/pq`;
+
+ await sendEmail({
+ to: vendor.email,
+ subject: emailSubject,
+ template: "pq-approved-vendor",
+ context: {
+ vendorName: vendor.vendorName,
+ projectId: pqSubmission.projectId,
+ projectName: projectName,
+ isProjectPQ: !!pqSubmission.projectId,
+ approvedDate: currentDate.toLocaleString(),
+ portalUrl,
+ }
+ });
+ } catch (emailError) {
+ console.error("Failed to send vendor notification:", emailError);
+ // 이메일 발송 실패가 전체 프로세스를 중단하지 않음
+ }
}
// 8. 캐시 무효화
@@ -2314,12 +2338,12 @@ export async function approveQMReviewAction({ }
// 2. 상태 확인 (QM_REVIEWING 상태만 승인 가능)
- if (pqSubmission.status !== "QM_REVIEWING") {
- return {
- ok: false,
- error: `Cannot approve QM review in current status: ${pqSubmission.status}`
- };
- }
+ // if (pqSubmission.status !== "QM_REVIEWING") {
+ // return {
+ // ok: false,
+ // error: `Cannot approve QM review in current status: ${pqSubmission.status}`
+ // };
+ // }
// 3. 벤더 정보 조회
const vendor = await db
@@ -2373,22 +2397,41 @@ export async function approveQMReviewAction({ .where(eq(vendors.id, vendorId));
}
- // 7. 실사 요청 생성 (QM 승인 후 실사 프로세스 시작)
- await db
- .insert(vendorInvestigations)
- .values({
- vendorId: vendorId,
- pqSubmissionId: pqSubmissionId,
- investigationStatus: "PLANNED",
- investigationMethod: "DOCUMENT_EVAL", // 기본값, 나중에 변경 가능
- });
+ // 7. 실사 상태 변경: QM 승인 시 QM_REVIEW_CONFIRMED로 전환
+ try {
+ const existingInvestigation = await db
+ .select({ id: vendorInvestigations.id })
+ .from(vendorInvestigations)
+ .where(eq(vendorInvestigations.pqSubmissionId, pqSubmissionId))
+ .then(rows => rows[0]);
+
+ if (existingInvestigation) {
+ await db
+ .update(vendorInvestigations)
+ .set({ investigationStatus: "QM_REVIEW_CONFIRMED", updatedAt: currentDate })
+ .where(eq(vendorInvestigations.id, existingInvestigation.id));
+ } else {
+ await db
+ .insert(vendorInvestigations)
+ .values({
+ vendorId: vendorId,
+ pqSubmissionId: pqSubmissionId,
+ investigationStatus: "QM_REVIEW_CONFIRMED",
+ investigationMethod: "DOCUMENT_EVAL",
+ requestedAt: currentDate,
+ updatedAt: currentDate,
+ });
+ }
+ } catch (e) {
+ console.error("Failed to set investigation QM_REVIEW_CONFIRMED on QM approve", e);
+ }
// 8. 벤더에게 이메일 알림 발송
if (vendor.email) {
try {
const emailSubject = pqSubmission.projectId
- ? `[eVCP] Project PQ Approved for ${projectName}`
- : "[eVCP] General PQ Approved";
+ ? `[eVCP] Project PQ QM Approved for ${projectName}`
+ : "[eVCP] General PQ QM Approved";
const portalUrl = `${host}/partners/pq`;
@@ -2525,7 +2568,25 @@ export async function rejectQMReviewAction({ .where(eq(vendors.id, vendorId));
}
- // 7. 벤더에게 이메일 알림 발송
+ // 7. 실사 상태 변경: QM 거절 시 CANCELED로 전환
+ try {
+ const existingInvestigation = await db
+ .select({ id: vendorInvestigations.id })
+ .from(vendorInvestigations)
+ .where(eq(vendorInvestigations.pqSubmissionId, pqSubmissionId))
+ .then(rows => rows[0]);
+
+ if (existingInvestigation) {
+ await db
+ .update(vendorInvestigations)
+ .set({ investigationStatus: "CANCELED", updatedAt: currentDate })
+ .where(eq(vendorInvestigations.id, existingInvestigation.id));
+ }
+ } catch (e) {
+ console.error("Failed to set investigation CANCELED on QM reject", e);
+ }
+
+ // 8. 벤더에게 이메일 알림 발송
if (vendor.email) {
try {
const emailSubject = pqSubmission.projectId
@@ -2553,11 +2614,12 @@ export async function rejectQMReviewAction({ }
}
- // 8. 캐시 무효화
+ // 9. 캐시 무효화
revalidateTag("vendors");
revalidateTag("vendor-status-counts");
revalidateTag("pq-submissions");
revalidateTag("vendor-pq-submissions");
+ revalidateTag("vendor-investigations");
revalidatePath("/evcp/pq_new");
return { ok: true };
@@ -2714,10 +2776,76 @@ export async function rejectPQAction({ }
}
+// PQ 보완요청 메일 발송 액션
+export async function requestPqSupplementAction({
+ pqSubmissionId,
+ vendorId,
+ comment,
+}: {
+ pqSubmissionId: number;
+ vendorId: number;
+ comment: string;
+}) {
+ unstable_noStore();
+ try {
+ const session = await getServerSession(authOptions);
+ const currentUserEmail = session?.user?.email || null;
+ const headersList = await headers();
+ const host = headersList.get('host') || 'localhost:3000';
+
+ // PQ/벤더/요청자 정보 조회
+ const pq = await db
+ .select({ id: vendorPQSubmissions.id, pqNumber: vendorPQSubmissions.pqNumber, requesterId: vendorPQSubmissions.requesterId, projectId: vendorPQSubmissions.projectId })
+ .from(vendorPQSubmissions)
+ .where(and(eq(vendorPQSubmissions.id, pqSubmissionId), eq(vendorPQSubmissions.vendorId, vendorId)))
+ .then(rows => rows[0]);
+ if (!pq) return { ok: false, error: 'PQ submission not found' };
+
+ const vendor = await db
+ .select({ vendorName: vendors.vendorName, email: vendors.email })
+ .from(vendors)
+ .where(eq(vendors.id, vendorId))
+ .then(rows => rows[0]);
+ if (!vendor?.email) return { ok: false, error: 'Vendor email not found' };
+
+ let requesterEmail: string | null = null;
+ if (pq.requesterId) {
+ const requester = await db
+ .select({ email: users.email })
+ .from(users)
+ .where(eq(users.id, pq.requesterId))
+ .then(rows => rows[0]);
+ requesterEmail = requester?.email || null;
+ }
+
+ const reviewUrl = `http://${host}/evcp/pq/${vendorId}/${pqSubmissionId}`;
+
+ await sendEmail({
+ to: vendor.email,
+ cc: [currentUserEmail, requesterEmail].filter(Boolean) as string[],
+ subject: `[eVCP] PQ 보완 요청: ${vendor.vendorName}`,
+ template: 'pq-supplement-request',
+ context: {
+ vendorName: vendor.vendorName,
+ pqNumber: pq.pqNumber,
+ comment,
+ reviewUrl,
+ },
+ });
+
+ revalidateTag('pq-submissions');
+ return { ok: true };
+ } catch (error) {
+ console.error('PQ supplement request error:', error);
+ return { ok: false, error: getErrorMessage(error) };
+ }
+}
+
// 실사 의뢰 생성 서버 액션
export async function requestInvestigationAction(
pqSubmissionIds: number[],
+ currentUser: { id: number; epId: string | null; email?: string },
data: {
qmManagerId: number,
forecastedAt: Date,
@@ -2727,10 +2855,7 @@ export async function requestInvestigationAction( ) {
try {
// 세션에서 요청자 정보 가져오기
- const session = await getServerSession(authOptions);
- const requesterId = session?.user?.id ? Number(session.user.id) : null;
-
- if (!requesterId) {
+ if (!currentUser.id) {
return { success: false, error: "인증된 사용자만 실사를 의뢰할 수 있습니다." };
}
@@ -2755,7 +2880,7 @@ export async function requestInvestigationAction( const now = new Date();
- // 각 PQ에 대한 실사 요청 생성 - 타입이 정확히 맞는지 확인
+ // 실사 요청 생성
const investigations = pqSubmissions.map((pq) => {
return {
vendorId: pq.vendorId,
@@ -2765,12 +2890,21 @@ export async function requestInvestigationAction( forecastedAt: data.forecastedAt,
investigationAddress: data.investigationAddress,
investigationNotes: data.investigationNotes || null,
- requesterId: requesterId,
+ requesterId: currentUser.id,
requestedAt: now,
createdAt: now,
updatedAt: now,
};
});
+ //PQ 제출 정보 업데이트, status를 QM_REVIEWING로 업데이트
+ await tx
+ .update(vendorPQSubmissions)
+ .set({
+ status: "QM_REVIEWING",
+ updatedAt: now,
+ })
+ .where(inArray(vendorPQSubmissions.id, pqSubmissionIds));
+
// 실사 요청 저장
const created = await tx
|
