diff options
Diffstat (limited to 'lib/b-rfq/service.ts')
| -rw-r--r-- | lib/b-rfq/service.ts | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/lib/b-rfq/service.ts b/lib/b-rfq/service.ts new file mode 100644 index 00000000..f64eb46c --- /dev/null +++ b/lib/b-rfq/service.ts @@ -0,0 +1,291 @@ +'use server' + +import { revalidateTag, unstable_cache } from "next/cache" +import { count, desc, asc, and, or, gte, lte, ilike, eq } from "drizzle-orm" +import { filterColumns } from "@/lib/filter-columns" +import db from "@/db/db" +import { RfqDashboardView, bRfqs, projects, users } from "@/db/schema" // 실제 스키마 import 경로에 맞게 수정 +import { rfqDashboardView } from "@/db/schema" // 뷰 import +import type { SQL } from "drizzle-orm" +import { CreateRfqInput, GetRFQDashboardSchema, createRfqServerSchema } from "./validations" + +export async function getRFQDashboard(input: GetRFQDashboardSchema) { + return unstable_cache( + async () => { + try { + const offset = (input.page - 1) * input.perPage; + + const rfqFilterMapping = createRFQFilterMapping(); + const joinedTables = getRFQJoinedTables(); + + // 1) 고급 필터 조건 + let advancedWhere: SQL<unknown> | undefined = undefined; + if (input.filters && input.filters.length > 0) { + advancedWhere = filterColumns({ + table: rfqDashboardView, + filters: input.filters, + joinOperator: input.joinOperator || 'and', + joinedTables, + customColumnMapping: rfqFilterMapping, + }); + } + + // 2) 기본 필터 조건 + let basicWhere: SQL<unknown> | undefined = undefined; + if (input.basicFilters && input.basicFilters.length > 0) { + basicWhere = filterColumns({ + table: rfqDashboardView, + filters: input.basicFilters, + joinOperator: input.basicJoinOperator || 'and', + joinedTables, + customColumnMapping: rfqFilterMapping, + }); + } + + // 3) 글로벌 검색 조건 + let globalWhere: SQL<unknown> | undefined = undefined; + if (input.search) { + const s = `%${input.search}%`; + + const validSearchConditions: SQL<unknown>[] = []; + + const rfqCodeCondition = ilike(rfqDashboardView.rfqCode, s); + if (rfqCodeCondition) validSearchConditions.push(rfqCodeCondition); + + const descriptionCondition = ilike(rfqDashboardView.description, s); + if (descriptionCondition) validSearchConditions.push(descriptionCondition); + + const projectNameCondition = ilike(rfqDashboardView.projectName, s); + if (projectNameCondition) validSearchConditions.push(projectNameCondition); + + const projectCodeCondition = ilike(rfqDashboardView.projectCode, s); + if (projectCodeCondition) validSearchConditions.push(projectCodeCondition); + + const picNameCondition = ilike(rfqDashboardView.picName, s); + if (picNameCondition) validSearchConditions.push(picNameCondition); + + const packageNoCondition = ilike(rfqDashboardView.packageNo, s); + if (packageNoCondition) validSearchConditions.push(packageNoCondition); + + const packageNameCondition = ilike(rfqDashboardView.packageName, s); + if (packageNameCondition) validSearchConditions.push(packageNameCondition); + + if (validSearchConditions.length > 0) { + globalWhere = or(...validSearchConditions); + } + } + + + + // 6) 최종 WHERE 조건 생성 + const whereConditions: SQL<unknown>[] = []; + + if (advancedWhere) whereConditions.push(advancedWhere); + if (basicWhere) whereConditions.push(basicWhere); + if (globalWhere) whereConditions.push(globalWhere); + + const finalWhere = whereConditions.length > 0 ? and(...whereConditions) : undefined; + + // 7) 전체 데이터 수 조회 + const totalResult = await db + .select({ count: count() }) + .from(rfqDashboardView) + .where(finalWhere); + + const total = totalResult[0]?.count || 0; + + if (total === 0) { + return { data: [], pageCount: 0, total: 0 }; + } + + console.log(total) + + // 8) 정렬 및 페이징 처리된 데이터 조회 + const orderByColumns = input.sort.map((sort) => { + const column = sort.id as keyof typeof rfqDashboardView.$inferSelect; + return sort.desc ? desc(rfqDashboardView[column]) : asc(rfqDashboardView[column]); + }); + + if (orderByColumns.length === 0) { + orderByColumns.push(desc(rfqDashboardView.createdAt)); + } + + const rfqData = await db + .select() + .from(rfqDashboardView) + .where(finalWhere) + .orderBy(...orderByColumns) + .limit(input.perPage) + .offset(offset); + + const pageCount = Math.ceil(total / input.perPage); + + return { data: rfqData, pageCount, total }; + } catch (err) { + console.error("Error in getRFQDashboard:", err); + return { data: [], pageCount: 0, total: 0 }; + } + }, + [JSON.stringify(input)], + { + revalidate: 3600, + tags: ["rfq-dashboard"], + } + )(); +} + +// 헬퍼 함수들 +function createRFQFilterMapping() { + return { + // 뷰의 컬럼명과 실제 필터링할 컬럼 매핑 + rfqCode: rfqDashboardView.rfqCode, + description: rfqDashboardView.description, + status: rfqDashboardView.status, + projectName: rfqDashboardView.projectName, + projectCode: rfqDashboardView.projectCode, + picName: rfqDashboardView.picName, + packageNo: rfqDashboardView.packageNo, + packageName: rfqDashboardView.packageName, + dueDate: rfqDashboardView.dueDate, + overallProgress: rfqDashboardView.overallProgress, + createdAt: rfqDashboardView.createdAt, + }; +} + +function getRFQJoinedTables() { + return { + // 조인된 테이블 정보 (뷰이므로 실제로는 사용되지 않을 수 있음) + projects, + users, + }; +} + +// ================================================================ +// 3. RFQ Dashboard 타입 정의 +// ================================================================ + +async function generateNextSerial(picCode: string): Promise<string> { + try { + // 해당 picCode로 시작하는 RFQ 개수 조회 + const existingCount = await db + .select({ count: count() }) + .from(bRfqs) + .where(eq(bRfqs.picCode, picCode)) + + const nextSerial = (existingCount[0]?.count || 0) + 1 + return nextSerial.toString().padStart(5, '0') // 5자리로 패딩 + } catch (error) { + console.error("시리얼 번호 생성 오류:", error) + return "00001" // 기본값 + } + } + export async function createRfqAction(input: CreateRfqInput) { + try { + // 입력 데이터 검증 + const validatedData = createRfqServerSchema.parse(input) + + // RFQ 코드 자동 생성: N + picCode + 시리얼5자리 + const serialNumber = await generateNextSerial(validatedData.picCode) + const rfqCode = `N${validatedData.picCode}${serialNumber}` + + // 데이터베이스에 삽입 + const result = await db.insert(bRfqs).values({ + rfqCode, + projectId: validatedData.projectId, + dueDate: validatedData.dueDate, + status: "DRAFT", + picCode: validatedData.picCode, + picName: validatedData.picName || null, + EngPicName: validatedData.engPicName || null, + packageNo: validatedData.packageNo || null, + packageName: validatedData.packageName || null, + remark: validatedData.remark || null, + projectCompany: validatedData.projectCompany || null, + projectFlag: validatedData.projectFlag || null, + projectSite: validatedData.projectSite || null, + createdBy: validatedData.createdBy, + updatedBy: validatedData.updatedBy, + }).returning({ + id: bRfqs.id, + rfqCode: bRfqs.rfqCode, + }) + + // 관련 페이지 캐시 무효화 + revalidateTag("rfq-dashboard") + + + return { + success: true, + data: result[0], + message: "RFQ가 성공적으로 생성되었습니다", + } + + } catch (error) { + console.error("RFQ 생성 오류:", error) + + + return { + success: false, + error: "RFQ 생성에 실패했습니다", + } + } + } + + // RFQ 코드 중복 확인 액션 + export async function checkRfqCodeExists(rfqCode: string) { + try { + const existing = await db.select({ id: bRfqs.id }) + .from(bRfqs) + .where(eq(bRfqs.rfqCode, rfqCode)) + .limit(1) + + return existing.length > 0 + } catch (error) { + console.error("RFQ 코드 확인 오류:", error) + return false + } + } + + // picCode별 다음 예상 RFQ 코드 미리보기 + export async function previewNextRfqCode(picCode: string) { + try { + const serialNumber = await generateNextSerial(picCode) + return `N${picCode}${serialNumber}` + } catch (error) { + console.error("RFQ 코드 미리보기 오류:", error) + return `N${picCode}00001` + } + } + +const getBRfqById = async (id: number): Promise<RfqDashboardView | null> => { + // 1) RFQ 단건 조회 + const rfqsRes = await db + .select() + .from(rfqDashboardView) + .where(eq(rfqDashboardView.rfqId, id)) + .limit(1); + + if (rfqsRes.length === 0) return null; + const rfqRow = rfqsRes[0]; + + // 3) RfqWithItems 형태로 반환 + const result: RfqDashboardView = { + ...rfqRow, + + }; + + return result; + }; + + + export const findBRfqById = async (id: number): Promise<RfqDashboardView | null> => { + try { + + const rfq = await getBRfqById(id); + + return rfq; + } catch (error) { + throw new Error('Failed to fetch user'); + } + }; +
\ No newline at end of file |
