summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/repository.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/techsales-rfq/repository.ts')
-rw-r--r--lib/techsales-rfq/repository.ts380
1 files changed, 380 insertions, 0 deletions
diff --git a/lib/techsales-rfq/repository.ts b/lib/techsales-rfq/repository.ts
new file mode 100644
index 00000000..260eef19
--- /dev/null
+++ b/lib/techsales-rfq/repository.ts
@@ -0,0 +1,380 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import {
+ techSalesRfqs,
+ techSalesVendorQuotations,
+ items,
+ vendors,
+ users
+} from "@/db/schema";
+import {
+ asc,
+ desc, count, SQL, sql
+} from "drizzle-orm";
+import { PgTransaction } from "drizzle-orm/pg-core";
+
+
+export type NewTechSalesRfq = typeof techSalesRfqs.$inferInsert;
+/**
+ * 기술영업 RFQ 생성
+ * ID 및 생성일 리턴
+ */
+export async function insertTechSalesRfq(
+ tx: PgTransaction<any, any, any>,
+ data: NewTechSalesRfq
+) {
+ return tx
+ .insert(techSalesRfqs)
+ .values(data)
+ .returning({ id: techSalesRfqs.id, createdAt: techSalesRfqs.createdAt });
+}
+
+/**
+ * 단건/복수 조회 시 공통으로 사용 가능한 SELECT 함수 예시
+ * - 트랜잭션(tx)을 받아서 사용하도록 구현
+ */
+export async function selectTechSalesRfqs(
+ tx: PgTransaction<any, any, any>,
+ params: {
+ where?: any;
+ orderBy?: (ReturnType<typeof asc> | ReturnType<typeof desc>)[];
+ offset?: number;
+ limit?: number;
+ }
+) {
+ const { where, orderBy, offset = 0, limit = 10 } = params;
+
+ return tx
+ .select()
+ .from(techSalesRfqs)
+ .where(where ?? undefined)
+ .orderBy(...(orderBy ?? []))
+ .offset(offset)
+ .limit(limit);
+}
+/** 총 개수 count */
+export async function countTechSalesRfqs(
+ tx: PgTransaction<any, any, any>,
+ where?: any
+) {
+ const res = await tx.select({ count: count() }).from(techSalesRfqs).where(where);
+ return res[0]?.count ?? 0;
+}
+
+/**
+ * RFQ 정보 직접 조인 조회 (뷰 대신 테이블 조인 사용)
+ */
+export async function selectTechSalesRfqsWithJoin(
+ tx: PgTransaction<any, any, any>,
+ params: {
+ where?: any;
+ orderBy?: (ReturnType<typeof asc> | ReturnType<typeof desc> | SQL<unknown>)[];
+ offset?: number;
+ limit?: number;
+ }
+) {
+ const { where, orderBy, offset = 0, limit = 10 } = params;
+
+ // 별칭 방식을 변경하여 select, join, where, orderBy 등을 순차적으로 수행
+ const query = tx.select({
+ // RFQ 기본 정보
+ id: techSalesRfqs.id,
+ rfqCode: techSalesRfqs.rfqCode,
+ itemId: techSalesRfqs.itemId,
+ itemName: items.itemName,
+ materialCode: techSalesRfqs.materialCode,
+
+ // 날짜 및 상태 정보
+ dueDate: techSalesRfqs.dueDate,
+ rfqSendDate: techSalesRfqs.rfqSendDate,
+ status: techSalesRfqs.status,
+
+ // 담당자 및 비고
+ picCode: techSalesRfqs.picCode,
+ remark: techSalesRfqs.remark,
+ cancelReason: techSalesRfqs.cancelReason,
+
+ // 생성/수정 정보
+ createdAt: techSalesRfqs.createdAt,
+ updatedAt: techSalesRfqs.updatedAt,
+
+ // 사용자 정보
+ createdBy: techSalesRfqs.createdBy,
+ createdByName: sql<string>`created_user.name`,
+ updatedBy: techSalesRfqs.updatedBy,
+ updatedByName: sql<string>`updated_user.name`,
+ sentBy: techSalesRfqs.sentBy,
+ sentByName: sql<string | null>`sent_user.name`,
+
+ // 프로젝트 정보 (스냅샷)
+ projectSnapshot: techSalesRfqs.projectSnapshot,
+ seriesSnapshot: techSalesRfqs.seriesSnapshot,
+
+ // 프로젝트 핵심 정보
+ pspid: sql<string>`${techSalesRfqs.projectSnapshot}->>'pspid'`,
+ projNm: sql<string>`${techSalesRfqs.projectSnapshot}->>'projNm'`,
+ sector: sql<string>`${techSalesRfqs.projectSnapshot}->>'sector'`,
+ projMsrm: sql<number>`(${techSalesRfqs.projectSnapshot}->>'projMsrm')::int`,
+ ptypeNm: sql<string>`${techSalesRfqs.projectSnapshot}->>'ptypeNm'`,
+
+ // 첨부파일 개수
+ attachmentCount: sql<number>`(
+ SELECT COUNT(*)
+ FROM tech_sales_attachments
+ WHERE tech_sales_attachments.tech_sales_rfq_id = ${techSalesRfqs.id}
+ )`,
+
+ // 벤더 견적 개수
+ quotationCount: sql<number>`(
+ SELECT COUNT(*)
+ FROM tech_sales_vendor_quotations
+ WHERE tech_sales_vendor_quotations.rfq_id = ${techSalesRfqs.id}
+ )`,
+ })
+ .from(techSalesRfqs)
+ .leftJoin(items, sql`${techSalesRfqs.itemId} = ${items.id}`)
+ .leftJoin(sql`${users} AS created_user`, sql`${techSalesRfqs.createdBy} = created_user.id`)
+ .leftJoin(sql`${users} AS updated_user`, sql`${techSalesRfqs.updatedBy} = updated_user.id`)
+ .leftJoin(sql`${users} AS sent_user`, sql`${techSalesRfqs.sentBy} = sent_user.id`);
+
+ // where 조건 적용
+ const queryWithWhere = where ? query.where(where) : query;
+
+ // orderBy 적용
+ const queryWithOrderBy = orderBy?.length
+ ? queryWithWhere.orderBy(...orderBy)
+ : queryWithWhere.orderBy(desc(techSalesRfqs.createdAt));
+
+ // offset과 limit 적용 후 실행
+ return queryWithOrderBy.offset(offset).limit(limit);
+}
+
+/**
+ * RFQ 개수 직접 조회 (뷰 대신 테이블 조인 사용)
+ */
+export async function countTechSalesRfqsWithJoin(
+ tx: PgTransaction<any, any, any>,
+ where?: any
+) {
+ const res = await tx
+ .select({ count: count() })
+ .from(techSalesRfqs)
+ .leftJoin(items, sql`${techSalesRfqs.itemId} = ${items.id}`)
+ .where(where ?? undefined);
+ return res[0]?.count ?? 0;
+}
+
+/**
+ * 벤더 견적서 직접 조인 조회 (뷰 대신 테이블 조인 사용)
+ */
+export async function selectTechSalesVendorQuotationsWithJoin(
+ tx: PgTransaction<any, any, any>,
+ params: {
+ where?: any;
+ orderBy?: (ReturnType<typeof asc> | ReturnType<typeof desc> | SQL<unknown>)[];
+ offset?: number;
+ limit?: number;
+ }
+) {
+ const { where, orderBy, offset = 0, limit = 10 } = params;
+
+ // 별칭 방식을 변경하여 select, join, where, orderBy 등을 순차적으로 수행
+ const query = tx.select({
+ // 견적 기본 정보
+ id: techSalesVendorQuotations.id,
+ rfqId: techSalesVendorQuotations.rfqId,
+ rfqCode: techSalesRfqs.rfqCode,
+ vendorId: techSalesVendorQuotations.vendorId,
+ vendorName: vendors.vendorName,
+ vendorCode: vendors.vendorCode,
+
+ // 견적 상세 정보
+ totalPrice: techSalesVendorQuotations.totalPrice,
+ currency: techSalesVendorQuotations.currency,
+ validUntil: techSalesVendorQuotations.validUntil,
+ status: techSalesVendorQuotations.status,
+ remark: techSalesVendorQuotations.remark,
+ rejectionReason: techSalesVendorQuotations.rejectionReason,
+
+ // 날짜 정보
+ submittedAt: techSalesVendorQuotations.submittedAt,
+ acceptedAt: techSalesVendorQuotations.acceptedAt,
+ createdAt: techSalesVendorQuotations.createdAt,
+ updatedAt: techSalesVendorQuotations.updatedAt,
+
+ // 생성/수정 사용자
+ createdBy: techSalesVendorQuotations.createdBy,
+ createdByName: sql<string | null>`created_user.name`,
+ updatedBy: techSalesVendorQuotations.updatedBy,
+ updatedByName: sql<string | null>`updated_user.name`,
+
+ // 프로젝트 정보
+ materialCode: techSalesRfqs.materialCode,
+ itemId: techSalesRfqs.itemId,
+ itemName: items.itemName,
+
+ // 프로젝트 핵심 정보
+ pspid: sql<string>`${techSalesRfqs.projectSnapshot}->>'pspid'`,
+ projNm: sql<string>`${techSalesRfqs.projectSnapshot}->>'projNm'`,
+ sector: sql<string>`${techSalesRfqs.projectSnapshot}->>'sector'`,
+
+ // 첨부파일 개수
+ attachmentCount: sql<number>`(
+ SELECT COUNT(*)
+ FROM tech_sales_rfq_comment_attachments
+ WHERE tech_sales_rfq_comment_attachments.quotation_id = ${techSalesVendorQuotations.id}
+ )`,
+ })
+ .from(techSalesVendorQuotations)
+ .leftJoin(techSalesRfqs, sql`${techSalesVendorQuotations.rfqId} = ${techSalesRfqs.id}`)
+ .leftJoin(vendors, sql`${techSalesVendorQuotations.vendorId} = ${vendors.id}`)
+ .leftJoin(items, sql`${techSalesRfqs.itemId} = ${items.id}`)
+ .leftJoin(sql`${users} AS created_user`, sql`${techSalesVendorQuotations.createdBy} = created_user.id`)
+ .leftJoin(sql`${users} AS updated_user`, sql`${techSalesVendorQuotations.updatedBy} = updated_user.id`);
+
+ // where 조건 적용
+ const queryWithWhere = where ? query.where(where) : query;
+
+ // orderBy 적용
+ const queryWithOrderBy = orderBy?.length
+ ? queryWithWhere.orderBy(...orderBy)
+ : queryWithWhere.orderBy(desc(techSalesVendorQuotations.createdAt));
+
+ // offset과 limit 적용 후 실행
+ return queryWithOrderBy.offset(offset).limit(limit);
+}
+
+/**
+ * 벤더 견적서 개수 직접 조회 (뷰 대신 테이블 조인 사용)
+ */
+export async function countTechSalesVendorQuotationsWithJoin(
+ tx: PgTransaction<any, any, any>,
+ where?: any
+) {
+ const res = await tx
+ .select({ count: count() })
+ .from(techSalesVendorQuotations)
+ .leftJoin(techSalesRfqs, sql`${techSalesVendorQuotations.rfqId} = ${techSalesRfqs.id}`)
+ .leftJoin(vendors, sql`${techSalesVendorQuotations.vendorId} = ${vendors.id}`)
+ .where(where ?? undefined);
+ return res[0]?.count ?? 0;
+}
+
+/**
+ * RFQ 대시보드 데이터 직접 조인 조회 (뷰 대신 테이블 조인 사용)
+ */
+export async function selectTechSalesDashboardWithJoin(
+ tx: PgTransaction<any, any, any>,
+ params: {
+ where?: any;
+ orderBy?: (ReturnType<typeof asc> | ReturnType<typeof desc> | SQL<unknown>)[];
+ offset?: number;
+ limit?: number;
+ }
+) {
+ const { where, orderBy, offset = 0, limit = 10 } = params;
+
+ // 별칭 방식을 변경하여 select, join, where, orderBy 등을 순차적으로 수행
+ const query = tx.select({
+ // RFQ 기본 정보
+ id: techSalesRfqs.id,
+ rfqCode: techSalesRfqs.rfqCode,
+ status: techSalesRfqs.status,
+ dueDate: techSalesRfqs.dueDate,
+ rfqSendDate: techSalesRfqs.rfqSendDate,
+ materialCode: techSalesRfqs.materialCode,
+
+ // 아이템 정보
+ itemId: techSalesRfqs.itemId,
+ itemName: items.itemName,
+
+ // 프로젝트 정보
+ pspid: sql<string>`${techSalesRfqs.projectSnapshot}->>'pspid'`,
+ projNm: sql<string>`${techSalesRfqs.projectSnapshot}->>'projNm'`,
+ sector: sql<string>`${techSalesRfqs.projectSnapshot}->>'sector'`,
+ projMsrm: sql<number>`(${techSalesRfqs.projectSnapshot}->>'projMsrm')::int`,
+ ptypeNm: sql<string>`${techSalesRfqs.projectSnapshot}->>'ptypeNm'`,
+
+ // 벤더 견적 통계
+ vendorCount: sql<number>`(
+ SELECT COUNT(DISTINCT vendor_id)
+ FROM tech_sales_vendor_quotations
+ WHERE tech_sales_vendor_quotations.rfq_id = ${techSalesRfqs.id}
+ )`,
+
+ quotationCount: sql<number>`(
+ SELECT COUNT(*)
+ FROM tech_sales_vendor_quotations
+ WHERE tech_sales_vendor_quotations.rfq_id = ${techSalesRfqs.id}
+ )`,
+
+ submittedQuotationCount: sql<number>`(
+ SELECT COUNT(*)
+ FROM tech_sales_vendor_quotations
+ WHERE tech_sales_vendor_quotations.rfq_id = ${techSalesRfqs.id}
+ AND tech_sales_vendor_quotations.status = 'Submitted'
+ )`,
+
+ minPrice: sql<string | null>`(
+ SELECT MIN(total_price)
+ FROM tech_sales_vendor_quotations
+ WHERE tech_sales_vendor_quotations.rfq_id = ${techSalesRfqs.id}
+ AND tech_sales_vendor_quotations.status = 'Submitted'
+ )`,
+
+ maxPrice: sql<string | null>`(
+ SELECT MAX(total_price)
+ FROM tech_sales_vendor_quotations
+ WHERE tech_sales_vendor_quotations.rfq_id = ${techSalesRfqs.id}
+ AND tech_sales_vendor_quotations.status = 'Submitted'
+ )`,
+
+ avgPrice: sql<string | null>`(
+ SELECT AVG(total_price)
+ FROM tech_sales_vendor_quotations
+ WHERE tech_sales_vendor_quotations.rfq_id = ${techSalesRfqs.id}
+ AND tech_sales_vendor_quotations.status = 'Submitted'
+ )`,
+
+ // 첨부파일 통계
+ attachmentCount: sql<number>`(
+ SELECT COUNT(*)
+ FROM tech_sales_attachments
+ WHERE tech_sales_attachments.tech_sales_rfq_id = ${techSalesRfqs.id}
+ )`,
+
+ // 코멘트 통계
+ commentCount: sql<number>`(
+ SELECT COUNT(*)
+ FROM tech_sales_rfq_comments
+ WHERE tech_sales_rfq_comments.rfq_id = ${techSalesRfqs.id}
+ )`,
+
+ unreadCommentCount: sql<number>`(
+ SELECT COUNT(*)
+ FROM tech_sales_rfq_comments
+ WHERE tech_sales_rfq_comments.rfq_id = ${techSalesRfqs.id}
+ AND tech_sales_rfq_comments.is_read = false
+ )`,
+
+ // 생성/수정 정보
+ createdAt: techSalesRfqs.createdAt,
+ updatedAt: techSalesRfqs.updatedAt,
+ createdByName: sql<string>`created_user.name`,
+ })
+ .from(techSalesRfqs)
+ .leftJoin(items, sql`${techSalesRfqs.itemId} = ${items.id}`)
+ .leftJoin(sql`${users} AS created_user`, sql`${techSalesRfqs.createdBy} = created_user.id`);
+
+ // where 조건 적용
+ const queryWithWhere = where ? query.where(where) : query;
+
+ // orderBy 적용
+ const queryWithOrderBy = orderBy?.length
+ ? queryWithWhere.orderBy(...orderBy)
+ : queryWithWhere.orderBy(desc(techSalesRfqs.updatedAt));
+
+ // offset과 limit 적용 후 실행
+ return queryWithOrderBy.offset(offset).limit(limit);
+}
+