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.ts313
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) {