diff options
Diffstat (limited to 'lib/vendors/service.ts')
| -rw-r--r-- | lib/vendors/service.ts | 313 |
1 files changed, 216 insertions, 97 deletions
diff --git a/lib/vendors/service.ts b/lib/vendors/service.ts index 9362a88c..596a52a0 100644 --- a/lib/vendors/service.ts +++ b/lib/vendors/service.ts @@ -9,7 +9,8 @@ import crypto from 'crypto'; import { v4 as uuidv4 } from 'uuid'; import { saveDRMFile } from "@/lib/file-stroage"; import { decryptWithServerAction } from "@/components/drm/drmUtils"; - +import { rfqLastVendorResponses, vendorQuotationView } from "@/db/schema/rfqVendor"; +import { rfqsLast, rfqLastDetails } from "@/db/schema/rfqLast"; import { filterColumns } from "@/lib/filter-columns"; import { unstable_cache } from "@/lib/unstable-cache"; import { getErrorMessage } from "@/lib/handle-error"; @@ -1213,117 +1214,235 @@ const removeVendormaterialsSchema = z.object({ }) - export async function getRfqHistory(input: GetRfqHistorySchema, vendorId: number) { - return unstable_cache( - async () => { - try { - logger.info({ vendorId, input }, "Starting getRfqHistory"); + try { + logger.info({ vendorId, input }, "Starting getRfqHistory"); - const offset = (input.page - 1) * input.perPage; + const offset = (input.page - 1) * input.perPage; - // 기본 where 조건 (vendorId) - const vendorWhere = eq(vendorRfqView.vendorId, vendorId); - logger.debug({ vendorWhere }, "Vendor where condition"); + // 기본 where 조건 (vendorId) + const vendorWhere = eq(rfqLastVendorResponses.vendorId, vendorId); + logger.debug({ vendorWhere }, "Vendor where condition"); - // 고급 필터링 - const advancedWhere = filterColumns({ - table: vendorRfqView, - filters: input.filters, - joinOperator: input.joinOperator, - }); - logger.debug({ advancedWhere }, "Advanced where condition"); + // 고급 필터링 + const advancedWhere = filterColumns({ + table: rfqsLast, + filters: input.filters, + joinOperator: input.joinOperator, + customColumnMapping: { + projectCode: { table: projects, column: "code" }, + projectName: { table: projects, column: "name" }, + projectInfo: { table: projects, column: "code" }, + packageInfo: { table: rfqsLast, column: "packageNo" }, + currency: { table: rfqLastVendorResponses, column: "vendorCurrency" }, + totalAmount: { table: rfqLastVendorResponses, column: "totalAmount" }, + paymentTerms: { table: rfqLastVendorResponses, column: "vendorPaymentTermsCode" }, + incoterms: { table: rfqLastVendorResponses, column: "vendorIncotermsCode" }, + shippingLocation: { table: rfqLastVendorResponses, column: "vendorPlaceOfShipping" }, + leadTime: { table: rfqLastVendorResponses, column: "vendorDeliveryDate" }, + contractInfo: { table: rfqLastDetails, column: "contractNo" }, + rfqSendDate: { table: rfqsLast, column: "rfqSendDate" }, + submittedAt: { table: rfqLastVendorResponses, column: "submittedAt" }, + }, + }); + logger.debug({ advancedWhere }, "Advanced where condition"); - // 글로벌 검색 - let globalWhere; - if (input.search) { - const s = `%${input.search}%`; - globalWhere = or( - ilike(vendorRfqView.rfqCode, s), - ilike(vendorRfqView.projectCode, s), - ilike(vendorRfqView.projectName, s) - ); - logger.debug({ globalWhere, search: input.search }, "Global search condition"); - } + // 글로벌 검색 + let globalWhere; + if (input.search) { + const s = `%${input.search}%`; + globalWhere = or( + ilike(rfqsLast.rfqCode, s), + ilike(projects.code, s), + ilike(projects.name, s), + ilike(rfqsLast.rfqType, s), + ilike(rfqsLast.status, s) + ); + logger.debug({ globalWhere, search: input.search }, "Global search condition"); + } - const finalWhere = and( - advancedWhere, - globalWhere, - vendorWhere - ); - logger.debug({ finalWhere }, "Final where condition"); + const finalWhere = and( + advancedWhere, + globalWhere, + vendorWhere + ); + logger.debug({ finalWhere }, "Final where condition"); - // 정렬 조건 - const orderBy = - input.sort.length > 0 - ? input.sort.map((item) => - item.desc ? desc(rfqs[item.id]) : asc(rfqs[item.id]) - ) - : [desc(rfqs.createdAt)]; - logger.debug({ orderBy }, "Order by condition"); + // 정렬 조건 - 동적 매핑 + const sortFieldMap: Record<string, any> = { + rfqType: rfqsLast.rfqType, + status: rfqsLast.status, + rfqCode: rfqsLast.rfqCode, + projectInfo: projects.code, + projectCode: projects.code, + projectName: projects.name, + packageNo: rfqsLast.packageNo, + packageName: rfqsLast.packageName, + packageInfo: rfqsLast.packageNo, + materialInfo: projects.code, + majorItemMaterialCategory: sql<string | null>`COALESCE( + (SELECT material_category FROM rfq_pr_items WHERE rfqs_last_id = ${rfqsLast.id} AND major_yn = true LIMIT 1), + (SELECT material_category FROM rfq_pr_items WHERE rfqs_last_id = ${rfqsLast.id} LIMIT 1) + )`, + majorItemMaterialDescription: sql<string | null>`COALESCE( + (SELECT material_description FROM rfq_pr_items WHERE rfqs_last_id = ${rfqsLast.id} AND major_yn = true LIMIT 1), + (SELECT material_description FROM rfq_pr_items WHERE rfqs_last_id = ${rfqsLast.id} LIMIT 1) + )`, + currency: rfqLastVendorResponses.vendorCurrency, + totalAmount: rfqLastVendorResponses.totalAmount, + leadTime: rfqLastVendorResponses.vendorDeliveryDate, + paymentTerms: rfqLastVendorResponses.vendorPaymentTermsCode, + incoterms: rfqLastVendorResponses.vendorIncotermsCode, + shippingLocation: rfqLastVendorResponses.vendorPlaceOfShipping, + contractInfo: rfqLastDetails.contractNo, + rfqSendDate: rfqsLast.rfqSendDate, + submittedAt: rfqLastVendorResponses.submittedAt, + picName: rfqsLast.picName, + }; - // 트랜잭션으로 데이터 조회 - const { data, total } = await db.transaction(async (tx) => { - logger.debug("Starting transaction for RFQ history query"); + const orderBy = + input.sort.length > 0 + ? input.sort.map((item) => { + const field = sortFieldMap[item.id]; + if (!field) { + logger.warn({ sortField: item.id }, "Unknown sort field, using default"); + return desc(rfqsLast.rfqSendDate); + } + return item.desc ? desc(field) : asc(field); + }) + : [desc(rfqsLast.rfqSendDate)]; + logger.debug({ orderBy }, "Order by condition"); - const data = await selectRfqHistory(tx, { - where: finalWhere, - orderBy, - offset, - limit: input.perPage, - }); - logger.debug({ dataLength: data.length }, "RFQ history data fetched"); + // 트랜잭션으로 데이터 조회 + const { data, total } = await db.transaction(async (tx) => { + logger.debug("Starting transaction for RFQ history query"); - // RFQ 아이템 정보 조회 - const rfqIds = data.map(rfq => rfq.id); - const items = await tx - .select({ - rfqId: rfqItems.rfqId, - id: rfqItems.id, - itemCode: rfqItems.itemCode, - description: rfqItems.description, - quantity: rfqItems.quantity, - uom: rfqItems.uom, - }) - .from(rfqItems) - .where(inArray(rfqItems.rfqId, rfqIds)); + // RFQ History 데이터 조회 - rfqsLast 기준으로 조인 (bid-history와 동일한 패턴) + const rfqHistoryData = await tx + .select({ + id: rfqsLast.id, + rfqType: rfqsLast.rfqType, + status: rfqsLast.status, + rfqCode: rfqsLast.rfqCode, + projectCode: projects.code, + projectName: projects.name, + packageNo: rfqsLast.packageNo, + packageName: rfqsLast.packageName, + majorItemMaterialCategory: sql<string | null>`COALESCE( + (SELECT material_category FROM rfq_pr_items WHERE rfqs_last_id = ${rfqsLast.id} AND major_yn = true LIMIT 1), + (SELECT material_category FROM rfq_pr_items WHERE rfqs_last_id = ${rfqsLast.id} LIMIT 1) + )`, + majorItemMaterialDescription: sql<string | null>`COALESCE( + (SELECT material_description FROM rfq_pr_items WHERE rfqs_last_id = ${rfqsLast.id} AND major_yn = true LIMIT 1), + (SELECT material_description FROM rfq_pr_items WHERE rfqs_last_id = ${rfqsLast.id} LIMIT 1) + )`, + vendorCurrency: rfqLastVendorResponses.vendorCurrency, + totalAmount: rfqLastVendorResponses.totalAmount, + vendorPaymentTermsCode: rfqLastVendorResponses.vendorPaymentTermsCode, + vendorIncotermsCode: rfqLastVendorResponses.vendorIncotermsCode, + vendorPlaceOfShipping: rfqLastVendorResponses.vendorPlaceOfShipping, + vendorDeliveryDate: rfqLastVendorResponses.vendorDeliveryDate, + vendorContractDuration: rfqLastVendorResponses.vendorContractDuration, + contractNo: rfqLastDetails.contractNo, + contractStatus: rfqLastDetails.contractStatus, + contractCreatedAt: rfqLastDetails.contractCreatedAt, + paymentTermsCode: rfqLastDetails.paymentTermsCode, + incotermsCode: rfqLastDetails.incotermsCode, + placeOfShipping: rfqLastDetails.placeOfShipping, + rfqSendDate: rfqsLast.rfqSendDate, + submittedAt: rfqLastVendorResponses.submittedAt, + picName: rfqsLast.picName, + responseStatus: rfqLastVendorResponses.status, + responseVersion: rfqLastVendorResponses.responseVersion, + }) + .from(rfqsLast) + .leftJoin(rfqLastVendorResponses, and( + eq(rfqLastVendorResponses.rfqsLastId, rfqsLast.id), + eq(rfqLastVendorResponses.vendorId, vendorId) + )) + .leftJoin(rfqLastDetails, eq(rfqLastVendorResponses.rfqLastDetailsId, rfqLastDetails.id)) + .leftJoin(projects, eq(rfqsLast.projectId, projects.id)) + .where(and( + advancedWhere, + globalWhere + )) + .orderBy(...orderBy) + .limit(input.perPage) + .offset(offset); - // RFQ 데이터에 아이템 정보 추가 - const dataWithItems = data.map(rfq => ({ - ...rfq, - items: items.filter(item => item.rfqId === rfq.id), - })); + logger.debug({ dataLength: rfqHistoryData.length }, "RFQ history data fetched"); + + // 데이터 변환 + const data = rfqHistoryData.map(row => ({ + id: row.id, + rfqType: row.rfqType, + status: row.status, + rfqCode: ((): string | null => { + if (!row.rfqCode) return null; + const rev = row.responseVersion ? ` (Rev.${row.responseVersion})` : ''; + return `${row.rfqCode}${rev}`; + })(), + projectInfo: row.projectCode && row.projectName ? `${row.projectCode} (${row.projectName})` : row.projectCode || row.projectName, + packageInfo: row.packageNo && row.packageName ? `${row.packageNo} (${row.packageName})` : row.packageNo || row.packageName, + materialInfo: row.majorItemMaterialCategory && row.majorItemMaterialDescription ? `${row.majorItemMaterialCategory} (${row.majorItemMaterialDescription})` : row.majorItemMaterialCategory || row.majorItemMaterialDescription, + // 견적정보 세부 필드들 + currency: row.vendorCurrency, + totalAmount: row.totalAmount, + leadTime: row.vendorDeliveryDate ?? row.vendorContractDuration ?? null, + paymentTerms: row.vendorPaymentTermsCode ?? row.paymentTermsCode ?? null, + incoterms: row.vendorIncotermsCode ?? row.incotermsCode ?? null, + shippingLocation: row.vendorPlaceOfShipping ?? row.placeOfShipping ?? null, + contractInfo: ((): string | null => { + const parts: string[] = []; + if (row.contractNo) parts.push(String(row.contractNo)); + if (row.contractStatus) parts.push(String(row.contractStatus)); + if (row.contractCreatedAt) parts.push(new Date(row.contractCreatedAt).toISOString().split('T')[0]); + return parts.length ? parts.join(' / ') : null; + })(), + rfqSendDate: row.rfqSendDate, + submittedAt: row.submittedAt, + picName: row.picName, + vendorStatus: row.responseStatus ?? '미응답' + })); + + // Total count 조회 - rfqsLast 기준으로 조인 (bid-history와 동일한 패턴) + const total = await tx + .select({ count: sql<number>`count(*)` }) + .from(rfqsLast) + .leftJoin(rfqLastVendorResponses, and( + eq(rfqLastVendorResponses.rfqsLastId, rfqsLast.id), + eq(rfqLastVendorResponses.vendorId, vendorId) + )) + .leftJoin(rfqLastDetails, eq(rfqLastVendorResponses.rfqLastDetailsId, rfqLastDetails.id)) + .leftJoin(projects, eq(rfqsLast.projectId, projects.id)) + .where(and( + advancedWhere, + globalWhere + )); - const total = await countRfqHistory(tx, finalWhere); - logger.debug({ total }, "RFQ history total count"); + const totalCount = total[0]?.count ?? 0; + logger.debug({ totalCount }, "RFQ history total count"); - return { data: dataWithItems, total }; - }); + return { data, total: totalCount }; + }); - const pageCount = Math.ceil(total / input.perPage); - logger.info({ - vendorId, - dataLength: data.length, - total, - pageCount - }, "RFQ history query completed"); + const pageCount = Math.ceil(total / input.perPage); + logger.info({ + vendorId, + dataLength: data.length, + total, + pageCount + }, "RFQ history query completed"); - return { data, pageCount }; - } catch (err) { - logger.error({ - err, - vendorId, - stack: err instanceof Error ? err.stack : undefined - }, 'Error fetching RFQ history'); - return { data: [], pageCount: 0 }; - } - }, - [JSON.stringify({ input, vendorId })], - { - revalidate: 3600, - tags: ["rfq-history"], - } - )(); + return { data, pageCount }; + } catch (err) { + logger.error({ + err, + vendorId, + stack: err instanceof Error ? err.stack : undefined + }, 'Error fetching RFQ history'); + return { data: [], pageCount: 0 }; + } } export async function checkJoinPortal(taxID: string) { |
