summaryrefslogtreecommitdiff
path: root/lib/b-rfq/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/b-rfq/service.ts')
-rw-r--r--lib/b-rfq/service.ts291
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