summaryrefslogtreecommitdiff
path: root/db/schema
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-08-27 12:06:26 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-08-27 12:06:26 +0000
commit7548e2ad6948f1c6aa102fcac408bc6c9c0f9796 (patch)
tree8e66703ec821888ad51dcc242a508813a027bf71 /db/schema
parent7eac558470ef179dad626a8e82db5784fe86a556 (diff)
(대표님, 최겸) 기본계약, 입찰, 파일라우트, 계약서명라우트, 인포메이션, 메뉴설정, PQ(메일템플릿 관련)
Diffstat (limited to 'db/schema')
-rw-r--r--db/schema/basicContractDocumnet.ts142
-rw-r--r--db/schema/bidding.ts8
-rw-r--r--db/schema/legal.ts3
3 files changed, 145 insertions, 8 deletions
diff --git a/db/schema/basicContractDocumnet.ts b/db/schema/basicContractDocumnet.ts
index 869ce538..7a9c972d 100644
--- a/db/schema/basicContractDocumnet.ts
+++ b/db/schema/basicContractDocumnet.ts
@@ -1,4 +1,4 @@
-import { pgTable, pgView, text, timestamp, integer, varchar, boolean , unique} from 'drizzle-orm/pg-core';
+import { pgTable, pgView, text, timestamp, integer, varchar, boolean , unique, date} from 'drizzle-orm/pg-core';
import { vendors } from './vendors';
import { eq, sql } from "drizzle-orm";
import { users } from './users';
@@ -34,16 +34,34 @@ export const basicContract = pgTable('basic_contract', {
templateId: integer('template_id').references(() => basicContractTemplates.id),
vendorId: integer('vendor_id').references(() => vendors.id),
requestedBy: integer('requested_by').references(() => users.id),
- status: text('status').notNull().default('PENDING'), // PENDING, COMPLETED, REJECTED
+
+ // 상태값을 더 세분화
+ status: text('status').notNull().default('PENDING'),
+ // PENDING: 초기 발송 상태
+ // VENDOR_SIGNED: 협력업체 서명 완료
+ // BUYER_SIGNED: 구매팀 서명 완료 (협력업체 서명 후)
+ // LEGAL_REVIEW_REQUESTED: 법무검토 요청
+ // LEGAL_REVIEW_COMPLETED: 법무검토 완료
+ // COMPLETED: 모든 과정 완료
+ // REJECTED: 거절됨
+
fileName: varchar("file_name", { length: 255 }).notNull(),
filePath: varchar("file_path", { length: 1024 }).notNull(),
+
+ // 새로 추가되는 필드들
+ deadline: date('deadline').notNull(), // 마감일 (요청일로부터 10일 후)
+ vendorSignedAt: timestamp('vendor_signed_at'), // 협력업체 서명일
+ buyerSignedAt: timestamp('buyer_signed_at'), // 구매팀 서명일
+ legalReviewRequestedAt: timestamp('legal_review_requested_at'), // 법무검토 요청일
+ legalReviewCompletedAt: timestamp('legal_review_completed_at'), // 법무검토 완료일
+
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
- completedAt: timestamp('completed_at'), // 계약 체결 완료 날짜 - 새로 추가할 필드
+ completedAt: timestamp('completed_at'), // 계약 체결 완료 날짜
+
});
-
// 기본 계약 요청 뷰
export const basicContractView = pgView('basic_contract_view').as((qb) => {
return qb
@@ -54,6 +72,14 @@ export const basicContractView = pgView('basic_contract_view').as((qb) => {
vendorId: sql<number | null>`${basicContract.vendorId}`.as('vendor_id'),
requestedBy: sql<number | null>`${basicContract.requestedBy}`.as('requested_by'),
status: sql<string>`${basicContract.status}`.as('basic_contract_status'),
+
+ // 새로 추가된 필드들
+ deadline: sql<string | null>`${basicContract.deadline}`.as('deadline'),
+ vendorSignedAt: sql<Date | null>`${basicContract.vendorSignedAt}`.as('vendor_signed_at'),
+ buyerSignedAt: sql<Date | null>`${basicContract.buyerSignedAt}`.as('buyer_signed_at'),
+ legalReviewRequestedAt: sql<Date | null>`${basicContract.legalReviewRequestedAt}`.as('legal_review_requested_at'),
+ legalReviewCompletedAt: sql<Date | null>`${basicContract.legalReviewCompletedAt}`.as('legal_review_completed_at'),
+
createdAt: sql<Date>`${basicContract.createdAt}`.as('created_at'),
updatedAt: sql<Date>`${basicContract.updatedAt}`.as('updated_at'),
completedAt: sql<Date | null>`${basicContract.completedAt}`.as('completed_at'),
@@ -94,9 +120,115 @@ export const basicContractView = pgView('basic_contract_view').as((qb) => {
.leftJoin(users, eq(basicContract.requestedBy, users.id))
.leftJoin(basicContractTemplates, eq(basicContract.templateId, basicContractTemplates.id));
});
-
// 타입 정의
export type BasicContractTemplate = typeof basicContractTemplates.$inferSelect;
export type BasicContract = typeof basicContract.$inferSelect;
export type BasicContractView = typeof basicContractView.$inferSelect;
+export const basicContractTemplateStatsView = pgView('basic_contract_template_stats_view').as((qb) => {
+ return qb
+ .select({
+ // 템플릿 기본 정보
+ templateId: sql<number>`${basicContractTemplates.id}`.as('template_id'),
+ templateName: sql<string>`${basicContractTemplates.templateName}`.as('template_name'),
+ revision: sql<number>`${basicContractTemplates.revision}`.as('revision'),
+ legalReviewRequired: sql<boolean>`${basicContractTemplates.legalReviewRequired}`.as('legal_review_required'),
+ validityPeriod: sql<number | null>`${basicContractTemplates.validityPeriod}`.as('validity_period'),
+ templateCreatedAt: sql<Date>`${basicContractTemplates.createdAt}`.as('template_created_at'),
+
+ // 통계 정보
+ // 발송건수 (전체 basicContract 수)
+ totalSentCount: sql<number>`COUNT(${basicContract.id})`.as('total_sent_count'),
+
+ // 지연건수 (데드라인이 지났는데 완료되지 않은 것)
+ overdueCount: sql<number>`
+ COUNT(CASE
+ WHEN ${basicContract.deadline} < CURRENT_DATE
+ AND ${basicContract.status} NOT IN ('COMPLETED', 'REJECTED')
+ THEN 1
+ END)
+ `.as('overdue_count'),
+
+ // 미서명건수 (PENDING 상태)
+ unsignedCount: sql<number>`
+ COUNT(CASE
+ WHEN ${basicContract.status} = 'PENDING'
+ THEN 1
+ END)
+ `.as('unsigned_count'),
+
+ // 서명완료건수 (협력업체만 - VENDOR_SIGNED 이상)
+ vendorSignedCount: sql<number>`
+ COUNT(CASE
+ WHEN ${basicContract.status} IN ('VENDOR_SIGNED', 'BUYER_SIGNED', 'LEGAL_REVIEW_REQUESTED', 'LEGAL_REVIEW_COMPLETED', 'COMPLETED')
+ THEN 1
+ END)
+ `.as('vendor_signed_count'),
+
+ // 법무요청건수
+ legalRequestCount: sql<number>`
+ COUNT(CASE
+ WHEN ${basicContract.status} IN ('LEGAL_REVIEW_REQUESTED', 'LEGAL_REVIEW_COMPLETED')
+ THEN 1
+ END)
+ `.as('legal_request_count'),
+
+ // 법무완료건수
+ legalCompletedCount: sql<number>`
+ COUNT(CASE
+ WHEN ${basicContract.status} = 'LEGAL_REVIEW_COMPLETED'
+ OR (${basicContract.status} = 'COMPLETED' AND ${basicContract.legalReviewCompletedAt} IS NOT NULL)
+ THEN 1
+ END)
+ `.as('legal_completed_count'),
+
+ // 계약완료건수
+ contractCompletedCount: sql<number>`
+ COUNT(CASE
+ WHEN ${basicContract.status} = 'COMPLETED'
+ THEN 1
+ END)
+ `.as('contract_completed_count'),
+
+ // 추가 유용한 통계
+ // 구매팀 서명완료건수
+ buyerSignedCount: sql<number>`
+ COUNT(CASE
+ WHEN ${basicContract.status} IN ('BUYER_SIGNED', 'LEGAL_REVIEW_REQUESTED', 'LEGAL_REVIEW_COMPLETED', 'COMPLETED')
+ THEN 1
+ END)
+ `.as('buyer_signed_count'),
+
+ // 거절건수
+ rejectedCount: sql<number>`
+ COUNT(CASE
+ WHEN ${basicContract.status} = 'REJECTED'
+ THEN 1
+ END)
+ `.as('rejected_count'),
+
+ // 평균 처리시간 (완료된 건만)
+ avgProcessingDays: sql<number>`
+ AVG(CASE
+ WHEN ${basicContract.status} = 'COMPLETED' AND ${basicContract.completedAt} IS NOT NULL
+ THEN EXTRACT(EPOCH FROM (${basicContract.completedAt} - ${basicContract.createdAt})) / 86400
+ END)
+ `.as('avg_processing_days'),
+
+ // 최근 활동일
+ lastActivityDate: sql<Date | null>`MAX(${basicContract.updatedAt})`.as('last_activity_date'),
+ })
+ .from(basicContractTemplates)
+ .leftJoin(basicContract, eq(basicContractTemplates.id, basicContract.templateId))
+ .where(eq(basicContractTemplates.status, 'ACTIVE'))
+ .groupBy(
+ basicContractTemplates.id,
+ basicContractTemplates.templateName,
+ basicContractTemplates.revision,
+ basicContractTemplates.legalReviewRequired,
+ basicContractTemplates.validityPeriod,
+ basicContractTemplates.createdAt
+ );
+});
+
+export type BasicContractTemplateStatsView = typeof basicContractTemplateStatsView.$inferSelect; \ No newline at end of file
diff --git a/db/schema/bidding.ts b/db/schema/bidding.ts
index 02e869b4..723bee32 100644
--- a/db/schema/bidding.ts
+++ b/db/schema/bidding.ts
@@ -150,6 +150,7 @@ export const biddings = pgTable('biddings', {
currency: varchar('currency', { length: 3 }).default('KRW'), // 통화
budget: decimal('budget', { precision: 15, scale: 2 }), // 예산
targetPrice: decimal('target_price', { precision: 15, scale: 2 }), // 내정가
+ targetPriceCalculationCriteria: text('target_price_calculation_criteria'), // 내정가 산정 기준
finalBidPrice: decimal('final_bid_price', { precision: 15, scale: 2 }), // 최종입찰가
// PR 정보
@@ -252,10 +253,10 @@ export const prItemsForBidding = pgTable('pr_items_for_bidding', {
updatedAt: timestamp('updated_at').defaultNow().notNull(),
})
-// 12. 입찰 조건 테이블 (구매자가 제시하는 조건들)
+// 12. 입찰 조건 테이블 (SHI 구매자가 제시하는 조건들)
export const biddingConditions = pgTable('bidding_conditions', {
id: serial('id').primaryKey(),
- biddingId: integer('bidding_id').references(() => biddings.id).notNull(),
+ biddingCompanyId: integer('bidding_company_id').references(() => biddingCompanies.id).notNull(),
// 지급조건
paymentTerms: text('payment_terms'), // 지급조건 옵션들 (JSON 배열)
@@ -299,7 +300,8 @@ export const biddingCompanies = pgTable('bidding_companies', {
finalQuoteAmount: decimal('final_quote_amount', { precision: 15, scale: 2 }),
finalQuoteSubmittedAt: timestamp('final_quote_submitted_at'),
isWinner: boolean('is_winner').default(false), // 낙찰 여부
-
+ isAttendingMeeting: boolean('is_attending_meeting'), // 사양설명회 참석 여부
+
// 기타
notes: text('notes'), // 특이사항
contactPerson: varchar('contact_person', { length: 100 }), // 업체 담당자
diff --git a/db/schema/legal.ts b/db/schema/legal.ts
index e4880517..3047bc2a 100644
--- a/db/schema/legal.ts
+++ b/db/schema/legal.ts
@@ -1,6 +1,7 @@
import { pgView,pgTable, serial, varchar, boolean, date, text, timestamp, integer, decimal } from 'drizzle-orm/pg-core';
import { vendors } from './vendors';
import { eq , sql, relations} from "drizzle-orm";
+import { basicContract } from './basicContractDocumnet';
// 법무 업무 테이블
export const legalWorks = pgTable('legal_works', {
@@ -38,6 +39,8 @@ export const legalWorks = pgTable('legal_works', {
// 메타데이터
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
+ basicContractId: integer("basic_contract_id")
+ .references(() => basicContract.id, { onDelete: "set null" }),
});
// 타입 정의