summaryrefslogtreecommitdiff
path: root/lib/tech-vendor-rfq-response/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tech-vendor-rfq-response/service.ts')
-rw-r--r--lib/tech-vendor-rfq-response/service.ts458
1 files changed, 0 insertions, 458 deletions
diff --git a/lib/tech-vendor-rfq-response/service.ts b/lib/tech-vendor-rfq-response/service.ts
deleted file mode 100644
index e6b67406..00000000
--- a/lib/tech-vendor-rfq-response/service.ts
+++ /dev/null
@@ -1,458 +0,0 @@
-'use server'
-
-import { revalidateTag } from "next/cache";
-import db from "@/db/db";
-import { and, desc, eq, inArray, isNull, or, sql } from "drizzle-orm";
-import { rfqAttachments, rfqComments, rfqItems, vendorResponses } from "@/db/schema/rfq";
-import { vendorResponsesView, vendorTechnicalResponses, vendorCommercialResponses, vendorResponseAttachments } from "@/db/schema/rfq";
-import { items, itemOffshoreTop, itemOffshoreHull } from "@/db/schema/items";
-import { GetRfqsForVendorsSchema } from "../rfqs-tech/validations";
-import { ItemData } from "./vendor-cbe-table/rfq-items-table/rfq-items-table";
-import * as z from "zod"
-
-export async function getRfqResponsesForVendor(input: GetRfqsForVendorsSchema, vendorId: number) {
- try {
- const offset = (input.page - 1) * input.perPage;
- const limit = input.perPage;
-
- // 1) 메인 쿼리: vendorResponsesView 사용
- const { rows, total } = await db.transaction(async (tx) => {
- // 검색 조건
- let globalWhere;
- if (input.search) {
- const s = `%${input.search}%`;
- globalWhere = or(
- sql`${vendorResponsesView.rfqCode} ILIKE ${s}`,
- sql`${vendorResponsesView.projectName} ILIKE ${s}`,
- sql`${vendorResponsesView.rfqDescription} ILIKE ${s}`
- );
- }
-
- // 협력업체 ID 필터링
- const mainWhere = and(eq(vendorResponsesView.vendorId, vendorId), globalWhere);
-
- // 정렬: 응답 시간순
- const orderBy = [desc(vendorResponsesView.respondedAt)];
-
- // (A) 데이터 조회
- const data = await tx
- .select()
- .from(vendorResponsesView)
- .where(mainWhere)
- .orderBy(...orderBy)
- .offset(offset)
- .limit(limit);
-
- // (B) 전체 개수 카운트
- const [{ count }] = await tx
- .select({
- count: sql<number>`count(*)`.as("count"),
- })
- .from(vendorResponsesView)
- .where(mainWhere);
-
- return { rows: data, total: Number(count) };
- });
-
- // 2) rfqId 고유 목록 추출
- const distinctRfqs = [...new Set(rows.map((r) => r.rfqId))];
- if (distinctRfqs.length === 0) {
- return { data: [], pageCount: 0 };
- }
-
- // 3) 추가 데이터 조회
- // 3-A) RFQ 아이템
- const itemsAll = await db
- .select({
- id: rfqItems.id,
- rfqId: rfqItems.rfqId,
- itemCode: rfqItems.itemCode,
- itemList: sql<string>`COALESCE(${itemOffshoreTop.itemList}, ${itemOffshoreHull.itemList})`.as('itemList'),
- subItemList: sql<string>`COALESCE(${itemOffshoreTop.subItemList}, ${itemOffshoreHull.subItemList})`.as('subItemList'),
- quantity: rfqItems.quantity,
- description: rfqItems.description,
- uom: rfqItems.uom,
- })
- .from(rfqItems)
- .leftJoin(itemOffshoreTop, eq(rfqItems.itemCode, itemOffshoreTop.itemCode))
- .leftJoin(itemOffshoreHull, eq(rfqItems.itemCode, itemOffshoreHull.itemCode))
- .where(inArray(rfqItems.rfqId, distinctRfqs));
-
- // 3-B) RFQ 첨부 파일 (협력업체용)
- const attachAll = await db
- .select()
- .from(rfqAttachments)
- .where(
- and(
- inArray(rfqAttachments.rfqId, distinctRfqs),
- isNull(rfqAttachments.vendorId)
- )
- );
-
- // 3-C) RFQ 코멘트
- const commAll = await db
- .select()
- .from(rfqComments)
- .where(
- and(
- inArray(rfqComments.rfqId, distinctRfqs),
- or(
- isNull(rfqComments.vendorId),
- eq(rfqComments.vendorId, vendorId)
- )
- )
- );
-
-
- // 3-E) 협력업체 응답 상세 - 기술
- const technicalResponsesAll = await db
- .select()
- .from(vendorTechnicalResponses)
- .where(
- inArray(
- vendorTechnicalResponses.responseId,
- rows.map((r) => r.responseId)
- )
- );
-
- // 3-F) 협력업체 응답 상세 - 상업
- const commercialResponsesAll = await db
- .select()
- .from(vendorCommercialResponses)
- .where(
- inArray(
- vendorCommercialResponses.responseId,
- rows.map((r) => r.responseId)
- )
- );
-
- // 3-G) 협력업체 응답 첨부 파일
- const responseAttachmentsAll = await db
- .select()
- .from(vendorResponseAttachments)
- .where(
- inArray(
- vendorResponseAttachments.responseId,
- rows.map((r) => r.responseId)
- )
- );
-
- // 4) 데이터 그룹화
- // RFQ 아이템 그룹화
- const itemsByRfqId = new Map<number, any[]>();
- for (const it of itemsAll) {
- if (!itemsByRfqId.has(it.rfqId)) {
- itemsByRfqId.set(it.rfqId, []);
- }
- itemsByRfqId.get(it.rfqId)!.push({
- id: it.id,
- itemCode: it.itemCode,
- itemList: it.itemList,
- subItemList: it.subItemList,
- quantity: it.quantity,
- description: it.description,
- uom: it.uom,
- });
- }
-
- // RFQ 첨부 파일 그룹화
- const attachByRfqId = new Map<number, any[]>();
- for (const att of attachAll) {
- const rid = att.rfqId!;
- if (!attachByRfqId.has(rid)) {
- attachByRfqId.set(rid, []);
- }
- attachByRfqId.get(rid)!.push({
- id: att.id,
- fileName: att.fileName,
- filePath: att.filePath,
- vendorId: att.vendorId,
- evaluationId: att.evaluationId,
- });
- }
-
- // RFQ 코멘트 그룹화
- const commByRfqId = new Map<number, any[]>();
- for (const c of commAll) {
- const rid = c.rfqId!;
- if (!commByRfqId.has(rid)) {
- commByRfqId.set(rid, []);
- }
- commByRfqId.get(rid)!.push({
- id: c.id,
- commentText: c.commentText,
- vendorId: c.vendorId,
- evaluationId: c.evaluationId,
- createdAt: c.createdAt,
- });
- }
-
-
- // 기술 응답 그룹화
- const techResponseByResponseId = new Map<number, any>();
- for (const tr of technicalResponsesAll) {
- techResponseByResponseId.set(tr.responseId, {
- id: tr.id,
- summary: tr.summary,
- notes: tr.notes,
- createdAt: tr.createdAt,
- updatedAt: tr.updatedAt,
- });
- }
-
- // 상업 응답 그룹화
- const commResponseByResponseId = new Map<number, any>();
- for (const cr of commercialResponsesAll) {
- commResponseByResponseId.set(cr.responseId, {
- id: cr.id,
- totalPrice: cr.totalPrice,
- currency: cr.currency,
- paymentTerms: cr.paymentTerms,
- incoterms: cr.incoterms,
- deliveryPeriod: cr.deliveryPeriod,
- warrantyPeriod: cr.warrantyPeriod,
- validityPeriod: cr.validityPeriod,
- priceBreakdown: cr.priceBreakdown,
- commercialNotes: cr.commercialNotes,
- createdAt: cr.createdAt,
- updatedAt: cr.updatedAt,
- });
- }
-
- // 응답 첨부 파일 그룹화
- const respAttachByResponseId = new Map<number, any[]>();
- for (const ra of responseAttachmentsAll) {
- const rid = ra.responseId!;
- if (!respAttachByResponseId.has(rid)) {
- respAttachByResponseId.set(rid, []);
- }
- respAttachByResponseId.get(rid)!.push({
- id: ra.id,
- fileName: ra.fileName,
- filePath: ra.filePath,
- attachmentType: ra.attachmentType,
- description: ra.description,
- uploadedAt: ra.uploadedAt,
- uploadedBy: ra.uploadedBy,
- });
- }
-
- // 5) 최종 데이터 결합
- const final = rows.map((row) => {
- return {
- // 응답 정보
- responseId: row.responseId,
- responseStatus: row.responseStatus,
- respondedAt: row.respondedAt,
-
- // RFQ 기본 정보
- rfqId: row.rfqId,
- rfqCode: row.rfqCode,
- rfqDescription: row.rfqDescription,
- rfqDueDate: row.rfqDueDate,
- rfqStatus: row.rfqStatus,
-
- rfqCreatedAt: row.rfqCreatedAt,
- rfqUpdatedAt: row.rfqUpdatedAt,
- rfqCreatedBy: row.rfqCreatedBy,
-
- // 프로젝트 정보
- projectId: row.projectId,
- projectCode: row.projectCode,
- projectName: row.projectName,
-
- // 협력업체 정보
- vendorId: row.vendorId,
- vendorName: row.vendorName,
- vendorCode: row.vendorCode,
-
- // RFQ 관련 데이터
- items: itemsByRfqId.get(row.rfqId) || [],
- attachments: attachByRfqId.get(row.rfqId) || [],
- comments: commByRfqId.get(row.rfqId) || [],
-
- // 평가 정보
- tbeEvaluation: row.tbeId ? {
- id: row.tbeId,
- result: row.tbeResult,
- } : null,
- cbeEvaluation: row.cbeId ? {
- id: row.cbeId,
- result: row.cbeResult,
- } : null,
-
- // 협력업체 응답 상세
- technicalResponse: techResponseByResponseId.get(row.responseId) || null,
- commercialResponse: commResponseByResponseId.get(row.responseId) || null,
- responseAttachments: respAttachByResponseId.get(row.responseId) || [],
-
- // 응답 상태 표시
- hasTechnicalResponse: row.hasTechnicalResponse,
- hasCommercialResponse: row.hasCommercialResponse,
- attachmentCount: row.attachmentCount || 0,
- };
- });
-
- const pageCount = Math.ceil(total / input.perPage);
- return { data: final, pageCount };
- } catch (err) {
- return { data: null, error: err instanceof Error ? err.message : "Unknown error" };
- }
-}
-
-
-export async function getItemsByRfqId(rfqId: number): Promise<ResponseType> {
- try {
- if (!rfqId || isNaN(Number(rfqId))) {
- return {
- success: false,
- error: "Invalid RFQ ID provided",
- }
- }
-
- // Query the database to get all items for the given RFQ ID
- const items = await db
- .select()
- .from(rfqItems)
- .where(eq(rfqItems.rfqId, rfqId))
- .orderBy(rfqItems.itemCode)
-
-
- return {
- success: true,
- data: items as ItemData[],
- }
- } catch (error) {
- console.error("Error fetching RFQ items:", error)
-
- return {
- success: false,
- error: error instanceof Error ? error.message : "Unknown error occurred when fetching RFQ items",
- }
- }
-}
-
-
-// Define the schema for validation
-const commercialResponseSchema = z.object({
- responseId: z.number(),
- vendorId: z.number(), // Added vendorId field
- responseStatus: z.enum(["PENDING", "IN_PROGRESS", "SUBMITTED", "REJECTED", "ACCEPTED"]),
- totalPrice: z.number().optional(),
- currency: z.string().default("USD"),
- paymentTerms: z.string().optional(),
- incoterms: z.string().optional(),
- deliveryPeriod: z.string().optional(),
- warrantyPeriod: z.string().optional(),
- validityPeriod: z.string().optional(),
- priceBreakdown: z.string().optional(),
- commercialNotes: z.string().optional(),
-})
-
-type CommercialResponseInput = z.infer<typeof commercialResponseSchema>
-
-interface ResponseType {
- success: boolean
- error?: string
- data?: any
-}
-
-export async function updateCommercialResponse(input: CommercialResponseInput): Promise<ResponseType> {
- try {
- // Validate input data
- const validated = commercialResponseSchema.parse(input)
-
- // Check if a commercial response already exists for this responseId
- const existingResponse = await db
- .select()
- .from(vendorCommercialResponses)
- .where(eq(vendorCommercialResponses.responseId, validated.responseId))
- .limit(1)
-
- const now = new Date()
-
- if (existingResponse.length > 0) {
- // Update existing record
- await db
- .update(vendorCommercialResponses)
- .set({
- responseStatus: validated.responseStatus,
- totalPrice: validated.totalPrice,
- currency: validated.currency,
- paymentTerms: validated.paymentTerms,
- incoterms: validated.incoterms,
- deliveryPeriod: validated.deliveryPeriod,
- warrantyPeriod: validated.warrantyPeriod,
- validityPeriod: validated.validityPeriod,
- priceBreakdown: validated.priceBreakdown,
- commercialNotes: validated.commercialNotes,
- updatedAt: now,
- })
- .where(eq(vendorCommercialResponses.responseId, validated.responseId))
-
- } else {
- // Return error instead of creating a new record
- return {
- success: false,
- error: "해당 응답 ID에 대한 상업 응답 정보를 찾을 수 없습니다."
- }
- }
-
- // Also update the main vendor response status if submitted
- if (validated.responseStatus === "SUBMITTED") {
- // Get the vendor response
- const vendorResponseResult = await db
- .select()
- .from(vendorResponses)
- .where(eq(vendorResponses.id, validated.responseId))
- .limit(1)
-
- if (vendorResponseResult.length > 0) {
- // Update the main response status to RESPONDED
- await db
- .update(vendorResponses)
- .set({
- responseStatus: "RESPONDED",
- updatedAt: now,
- })
- .where(eq(vendorResponses.id, validated.responseId))
- }
- }
-
-
- return {
- success: true,
- data: { responseId: validated.responseId }
- }
-
- } catch (error) {
- console.error("Error updating commercial response:", error)
-
- if (error instanceof z.ZodError) {
- return {
- success: false,
- error: "유효하지 않은 데이터가 제공되었습니다."
- }
- }
-
- return {
- success: false,
- error: error instanceof Error ? error.message : "Unknown error occurred"
- }
- }
-}
-// Helper function to get responseId from rfqId and vendorId
-export async function getCommercialResponseByResponseId(responseId: number): Promise<any | null> {
- try {
- const response = await db
- .select()
- .from(vendorCommercialResponses)
- .where(eq(vendorCommercialResponses.responseId, responseId))
- .limit(1)
-
- return response.length > 0 ? response[0] : null
- } catch (error) {
- console.error("Error getting commercial response:", error)
- return null
- }
-} \ No newline at end of file