diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-06 04:23:40 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-06 04:23:40 +0000 |
| commit | de2ac5a2860bc25180971e7a11f852d9d44675b7 (patch) | |
| tree | b931c363f2cb19e177a0a7b17190d5de2a82d709 /db/schema | |
| parent | 6c549b0f264e9be4d60af38f9efc05b189d6849f (diff) | |
(대표님) 정기평가, 법적검토, 정책, 가입관련 처리 및 관련 컴포넌트 추가, 메뉴 변경
Diffstat (limited to 'db/schema')
| -rw-r--r-- | db/schema/consent.ts | 138 | ||||
| -rw-r--r-- | db/schema/gtc.ts | 4 | ||||
| -rw-r--r-- | db/schema/index.ts | 2 | ||||
| -rw-r--r-- | db/schema/legal.ts | 276 | ||||
| -rw-r--r-- | db/schema/users.ts | 10 |
5 files changed, 427 insertions, 3 deletions
diff --git a/db/schema/consent.ts b/db/schema/consent.ts new file mode 100644 index 00000000..a02d5575 --- /dev/null +++ b/db/schema/consent.ts @@ -0,0 +1,138 @@ +// schema/consent.ts +import { + pgTable, + integer, + varchar, + boolean, + timestamp, + text, + json, + pgEnum, + uniqueIndex, + index, + } from "drizzle-orm/pg-core"; +import { users } from "./users"; + + // 동의 타입 enum + export const consentTypeEnum = pgEnum("consent_type", [ + "privacy_policy", + "terms_of_service", + "marketing", + "optional" + ]); + + // 동의 액션 enum + export const consentActionEnum = pgEnum("consent_action", [ + "consent", + "revoke", + "update" + ]); + + // 정책 타입 enum + export const policyTypeEnum = pgEnum("policy_type", [ + "privacy_policy", + "terms_of_service" + ]); + + // 1. 정책 버전 관리 테이블 + export const policyVersions = pgTable("policy_versions", { + id: integer("id").primaryKey().generatedAlwaysAsIdentity(), + policyType: policyTypeEnum("policy_type").notNull(), + version: varchar("version", { length: 20 }).notNull(), + content: text("content").notNull(), + effectiveDate: timestamp("effective_date", { withTimezone: true }).notNull(), + isCurrent: boolean("is_current").default(false).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, (table) => { + return { + // 정책 타입과 버전의 유니크 조합 + policyVersionUniqueIdx: uniqueIndex("policy_versions_type_version_idx") + .on(table.policyType, table.version), + // 현재 버전 조회용 인덱스 + currentPolicyIdx: index("policy_versions_current_idx") + .on(table.isCurrent), + // 효력 발생일 인덱스 + effectiveDateIdx: index("policy_versions_effective_date_idx") + .on(table.effectiveDate), + }; + }); + + // 2. 사용자 동의 내역 테이블 + export const userConsents = pgTable("user_consents", { + id: integer("id").primaryKey().generatedAlwaysAsIdentity(), + userId: integer("user_id") + .references(() => users.id, { onDelete: "cascade" }) + .notNull(), + consentType: consentTypeEnum("consent_type").notNull(), + consentStatus: boolean("consent_status").default(false).notNull(), + policyVersion: varchar("policy_version", { length: 20 }).notNull(), + consentedAt: timestamp("consented_at", { withTimezone: true }) + .defaultNow() + .notNull(), + ipAddress: varchar("ip_address", { length: 45 }), // IPv6 지원 + userAgent: text("user_agent"), + revokedAt: timestamp("revoked_at", { withTimezone: true }), + revokeReason: text("revoke_reason"), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, (table) => { + return { + // 사용자별 동의 타입 조회용 인덱스 + userConsentTypeIdx: index("user_consents_user_type_idx") + .on(table.userId, table.consentType), + // 동의 시점 인덱스 + consentedAtIdx: index("user_consents_consented_at_idx") + .on(table.consentedAt), + // 정책 버전별 인덱스 + policyVersionIdx: index("user_consents_policy_version_idx") + .on(table.policyVersion), + }; + }); + + // 3. 동의 로그 테이블 (모든 동의/철회 이력 보관) + export const consentLogs = pgTable("consent_logs", { + id: integer("id").primaryKey().generatedAlwaysAsIdentity(), + userId: integer("user_id") + .references(() => users.id, { onDelete: "cascade" }) + .notNull(), + consentType: consentTypeEnum("consent_type").notNull(), + action: consentActionEnum("action").notNull(), + oldStatus: boolean("old_status"), + newStatus: boolean("new_status").notNull(), + policyVersion: varchar("policy_version", { length: 20 }).notNull(), + ipAddress: varchar("ip_address", { length: 45 }), + userAgent: text("user_agent"), + actionTimestamp: timestamp("action_timestamp", { withTimezone: true }) + .defaultNow() + .notNull(), + additionalData: json("additional_data"), // 추가 메타데이터 + }, (table) => { + return { + // 사용자별 액션 시간 순 조회용 인덱스 + userActionTimestampIdx: index("consent_logs_user_action_timestamp_idx") + .on(table.userId, table.actionTimestamp), + // 동의 타입별 인덱스 + consentTypeIdx: index("consent_logs_consent_type_idx") + .on(table.consentType), + // 액션 타입별 인덱스 + actionIdx: index("consent_logs_action_idx") + .on(table.action), + }; + }); + + + export type PolicyVersion = typeof policyVersions.$inferSelect; + export type NewPolicyVersion = typeof policyVersions.$inferInsert; + + export type UserConsent = typeof userConsents.$inferSelect; + export type NewUserConsent = typeof userConsents.$inferInsert; + + export type ConsentLog = typeof consentLogs.$inferSelect; + export type NewConsentLog = typeof consentLogs.$inferInsert; +
\ No newline at end of file diff --git a/db/schema/gtc.ts b/db/schema/gtc.ts index 610804a3..e91ed44f 100644 --- a/db/schema/gtc.ts +++ b/db/schema/gtc.ts @@ -569,7 +569,7 @@ export const gtcNegotiationHistory = pgTable("gtc_negotiation_history", { }) // 관계 정의 -export const vendorsRelations = relations(vendors, ({ many, one }) => ({ +export const vendorsRelationsForGTC = relations(vendors, ({ many, one }) => ({ vendorDocuments: many(gtcVendorDocuments) })) @@ -669,8 +669,6 @@ export const gtcNegotiationHistoryRelations = relations(gtcNegotiationHistory, ( })) // 타입 정의 -export type Vendor = typeof vendors.$inferSelect -export type NewVendor = typeof vendors.$inferInsert export type GtcTemplate = typeof gtcTemplates.$inferSelect export type NewGtcTemplate = typeof gtcTemplates.$inferInsert diff --git a/db/schema/index.ts b/db/schema/index.ts index 87ea224a..8af7d74a 100644 --- a/db/schema/index.ts +++ b/db/schema/index.ts @@ -31,6 +31,8 @@ export * from './notification'; export * from './templates'; export * from './gtc'; export * from './docu-list-rule'; +export * from './legal'; +export * from './consent'; // 부서별 도메인 할당 관리 export * from './departmentDomainAssignments'; diff --git a/db/schema/legal.ts b/db/schema/legal.ts new file mode 100644 index 00000000..e4880517 --- /dev/null +++ b/db/schema/legal.ts @@ -0,0 +1,276 @@ +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"; + +// 법무 업무 테이블 +export const legalWorks = pgTable('legal_works', { + id: serial('id').primaryKey(), + + // 구분 (CP, GTC, 기타 등) + category: varchar('category', { length: 50 }).notNull(), + + // 상태 (답변완료, 담당자배정, 검토요청 등) + status: varchar('status', { length: 100 }).notNull(), + + vendorId: integer("company_id") + .references(() => vendors.id, { onDelete: "set null" }), + + // 벤더 정보 + vendorCode: varchar('vendor_code', { length: 50 }), + vendorName: varchar('vendor_name', { length: 200 }).notNull(), + + // 긴급여부 + isUrgent: boolean('is_urgent').default(false).notNull(), + + // 날짜 필드들 + requestDate: date('request_date'), // 답변요청일 + consultationDate: date('consultation_date'), // 의뢰일 + expectedAnswerDate: date('expected_answer_date'), // 답변예정일 + legalCompletionDate: date('legal_completion_date'), // 법무완료일 + + // 담당자 정보 + reviewer: varchar('reviewer', { length: 100 }), // 검토요청자 + legalResponder: varchar('legal_responder', { length: 100 }), // 법무답변자 + + // 첨부파일 여부 + hasAttachment: boolean('has_attachment').default(false).notNull(), + + // 메타데이터 + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + +// 타입 정의 +export type LegalWork = typeof legalWorks.$inferSelect; +export type NewLegalWork = typeof legalWorks.$inferInsert; + +export type LegalWorkCategory = typeof LEGAL_WORK_CATEGORIES[number]; +export type LegalWorkStatus = typeof LEGAL_WORK_STATUSES[number]; + + +// 법무 검토 요청 테이블 +export const legalWorkRequests = pgTable('legal_work_requests', { + id: serial('id').primaryKey(), + legalWorkId: integer('legal_work_id').references(() => legalWorks.id, { onDelete: 'cascade' }).notNull(), + + // 검토부문 (준법문의 or 법무검토) + reviewDepartment: varchar('review_department', { length: 50 }).notNull(), // '준법문의' | '법무검토' + + // 문의종류 (법무검토 선택시만) + inquiryType: varchar('inquiry_type', { length: 50 }), // '국내계약' | '국내자문' | '해외계약' | '해외자문' + + // 제목 + title: varchar('title', { length: 500 }).notNull(), + + // 요청내용 + requestContent: text('request_content').notNull(), + + // ===== 준법문의 관련 필드들 ===== + + // 공개여부 (준법문의 선택시) + isPublic: boolean('is_public').default(false), // 기본값: 비공개 + + // ===== 법무검토 관련 공통 필드들 ===== + + // 계약명/프로젝트명 (국내계약/해외계약/해외자문 선택시) + contractProjectName: varchar('contract_project_name', { length: 300 }), + + // 계약서 종류 (국내계약/해외계약/해외자문 선택시) + contractType: varchar('contract_type', { length: 100 }), + + // 계약금액 (국내계약/해외계약/해외자문 선택시) + contractAmount: decimal('contract_amount', { precision: 15, scale: 2 }), + + // ===== 국내계약 전용 필드들 ===== + + // 계약상대방 (국내계약 선택시) + contractCounterparty: varchar('contract_counterparty', { length: 200 }), + + // 계약상대방 구분 (법인/개인) + counterpartyType: varchar('counterparty_type', { length: 20 }), // '법인' | '개인' + + // 계약기간 (국내계약 선택시) + contractPeriod: varchar('contract_period', { length: 200 }), + + // ===== 자문 관련 필드들 ===== + + // 사실관계 (국내자문/해외자문 선택시) + factualRelation: text('factual_relation'), + + // ===== 해외 관련 필드들 ===== + + // 프로젝트번호 (해외계약/해외자문 선택시) + projectNumber: varchar('project_number', { length: 100 }), + + // 선주/발주처 (해외계약/해외자문 선택시) + shipownerOrderer: varchar('shipowner_orderer', { length: 200 }), + + // 준거법 (해외계약/해외자문 선택시) + governingLaw: varchar('governing_law', { length: 100 }), + + // 프로젝트종류 (해외계약/해외자문 선택시) + projectType: varchar('project_type', { length: 100 }), + + // 메타데이터 + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + +// 법무 회신 테이블 +export const legalWorkResponses = pgTable('legal_work_responses', { + id: serial('id').primaryKey(), + legalWorkId: integer('legal_work_id').references(() => legalWorks.id, { onDelete: 'cascade' }).notNull(), + + // 답변내용 + responseContent: text('response_content').notNull(), + + // 답변 관련 담당자들 + responseReviewer: varchar('response_reviewer', { length: 100 }), // 답변검토자 + responseConfirmer: varchar('response_confirmer', { length: 100 }), // 답변확인자 + responseApprover: varchar('response_approver', { length: 100 }), // 승인자 + + // 처리 시간들 + reviewedAt: timestamp('reviewed_at'), // 검토시간 + confirmedAt: timestamp('confirmed_at'), // 확인시간 + approvedAt: timestamp('approved_at'), // 승인시간 + + // 공개여부 + isPublic: boolean('is_public').default(false).notNull(), + + // 재검토 관련 + isReRevision: boolean('is_re_revision').default(false).notNull(), // 재검토 여부 + parentResponseId: integer('parent_response_id'), // 이전 답변 참조 + + // 메타데이터 + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + +// 첨부파일 테이블 +export const legalWorkAttachments = pgTable('legal_work_attachments', { + id: serial('id').primaryKey(), + legalWorkId: integer('legal_work_id').references(() => legalWorks.id, { onDelete: 'cascade' }).notNull(), + + fileName: varchar('file_name', { length: 255 }).notNull(), + originalFileName: varchar('original_file_name', { length: 255 }).notNull(), + filePath: varchar('file_path', { length: 500 }).notNull(), + fileSize: integer('file_size').notNull(), + mimeType: varchar('mime_type', { length: 100 }).notNull(), + + // 자동 생성 파일 여부 (Case1에서 PDF 자동 생성) + isAutoGenerated: boolean('is_auto_generated').default(false).notNull(), + + // 첨부파일 타입 (요청시 첨부 vs 답변시 첨부) + attachmentType: varchar('attachment_type', { length: 50 }).default('request'), // 'request' | 'response' + + createdAt: timestamp('created_at').defaultNow().notNull(), +}); + + +export type LegalWorkRequest = typeof legalWorkRequests.$inferSelect; +export type LegalWorkResponse = typeof legalWorkResponses.$inferSelect; +export type LegalWorkAttachment = typeof legalWorkAttachments.$inferSelect; + +// 상수 정의 +export const LEGAL_WORK_CATEGORIES = [ + 'CP', + 'GTC', + '기타' +] as const; + +export const LEGAL_WORK_STATUSES = [ + '신규등록', + '검토요청', + '담당자배정', + '검토중', + '답변완료', + '재검토요청', + '보류', + '취소' +] as const; + +export const REVIEW_DEPARTMENTS = [ + '준법문의', + '법무검토' +] as const; + +export const INQUIRY_TYPES = [ + '국내계약', + '국내자문', + '해외계약', + '해외자문' +] as const; + +export const COUNTERPARTY_TYPES = [ + '법인', + '개인' +] as const; + +export const SOURCE_TYPES = [ + 'interface', // 22번화면에서 이관 + 'manual' // 신규생성 +] as const; + +export const ATTACHMENT_TYPES = [ + 'request', // 요청시 첨부 + 'response' // 답변시 첨부 +] as const; + + + +export const legalWorksDetailView = pgView("legal_works_detail_view").as((qb) => { + return qb + .select({ + // legal_works 기본 필드들 + id: legalWorks.id, + category: legalWorks.category, + status: legalWorks.status, + vendorId: legalWorks.vendorId, + vendorCode: legalWorks.vendorCode, + vendorName: legalWorks.vendorName, + isUrgent: legalWorks.isUrgent, + requestDate: legalWorks.requestDate, + consultationDate: legalWorks.consultationDate, + expectedAnswerDate: legalWorks.expectedAnswerDate, + legalCompletionDate: legalWorks.legalCompletionDate, + reviewer: legalWorks.reviewer, + legalResponder: legalWorks.legalResponder, + hasAttachment: legalWorks.hasAttachment, + createdAt: legalWorks.createdAt, + updatedAt: legalWorks.updatedAt, + + // legal_work_requests 필드들 + reviewDepartment: legalWorkRequests.reviewDepartment, + inquiryType: legalWorkRequests.inquiryType, + title: legalWorkRequests.title, + requestContent: legalWorkRequests.requestContent, + isPublicRequest: legalWorkRequests.isPublic, + contractProjectName: legalWorkRequests.contractProjectName, + contractType: legalWorkRequests.contractType, + contractAmount: legalWorkRequests.contractAmount, + + + // 최신 답변 정보 (서브쿼리) + responseContent: sql<string | null>`( + SELECT response_content + FROM legal_work_responses lwr_latest + WHERE lwr_latest.legal_work_id = ${legalWorks.id} + ORDER BY lwr_latest.created_at DESC + LIMIT 1 + )`.as('response_content'), + + // 첨부파일 개수 + attachmentCount: sql<number>`( + SELECT COUNT(*)::integer + FROM legal_work_attachments lwa + WHERE lwa.legal_work_id = ${legalWorks.id} + )`.as('attachment_count'), + }) + .from(legalWorks) + .leftJoin(legalWorkRequests, sql`${legalWorks.id} = ${legalWorkRequests.legalWorkId}`) + .leftJoin(vendors, sql`${legalWorks.vendorId} = ${vendors.id}`); +}); + +// 타입 추출 +export type LegalWorksDetailView = typeof legalWorksDetailView.$inferSelect; + diff --git a/db/schema/users.ts b/db/schema/users.ts index 5ea399b4..0d727bb4 100644 --- a/db/schema/users.ts +++ b/db/schema/users.ts @@ -49,6 +49,16 @@ export const users = pgTable("users", { deactivatedAt: timestamp("deactivated_at", { withTimezone: true }), deactivationReason: varchar("deactivation_reason", { length: 50 }), // 'INACTIVE', 'ADMIN', 'GDPR' 등 + // ✨ 새로 추가: 동의 관련 필드들 + lastConsentUpdate: timestamp("last_consent_update", { withTimezone: true }), + consentVersion: varchar("consent_version", { length: 20 }), // 마지막 동의한 정책 버전 + requiresConsentUpdate: boolean("requires_consent_update").default(false).notNull(), + + // ✨ 새로 추가: 회원가입 관련 + // emailVerified: boolean("email_verified").default(false).notNull(), + // emailVerifiedAt: timestamp("email_verified_at", { withTimezone: true }), + // registrationCompleted: boolean("registration_completed").default(false).notNull(), + }, (table) => { return { emailIdx: uniqueIndex("users_email_idx").on(table.email), |
