summaryrefslogtreecommitdiff
path: root/db/schema
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-08-04 09:39:21 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-08-04 09:39:21 +0000
commit53ad72732f781e6c6d5ddb3776ea47aec010af8e (patch)
treee676287827f8634be767a674b8ad08b6ed7eb3e6 /db/schema
parent3e4d15271322397764601dee09441af8a5b3adf5 (diff)
(최겸) PQ/실사 수정 및 개발
Diffstat (limited to 'db/schema')
-rw-r--r--db/schema/pq.ts236
1 files changed, 179 insertions, 57 deletions
diff --git a/db/schema/pq.ts b/db/schema/pq.ts
index c5f8c732..580a45b3 100644
--- a/db/schema/pq.ts
+++ b/db/schema/pq.ts
@@ -1,12 +1,44 @@
import {
pgTable, serial, integer, varchar, text,
- timestamp, uniqueIndex, pgView
+ timestamp, uniqueIndex, pgView, boolean,
+ jsonb, decimal
} from "drizzle-orm/pg-core";
import { vendorTypes, vendors } from "./vendors";
import { projects } from "./projects";
import { sql, eq } from "drizzle-orm";
import { users } from "./users";
+// ---------------------------------------------------------------------------
+// PQ Lists – 관리자가 생성하는 PQ 목록 정의 (GENERAL / PROJECT / NON_INSPECTION)
+// ---------------------------------------------------------------------------
+
+export const pqLists = pgTable("pq_lists", {
+ id: serial("id").primaryKey(),
+
+ // 목록 명칭 – "General PQ", "Project PQ – EPC-1234" 등
+ name: varchar("name", { length: 255 }).notNull(),
+
+ // GENERAL | PROJECT | NON_INSPECTION
+ type: varchar("type", { length: 20 }).notNull(),
+
+ // 프로젝트 PQ의 경우 연결될 프로젝트
+ projectId: integer("project_id").references(() => projects.id),
+
+ // 삭제 플래그 (실제 데이터는 보존)
+ isDeleted: boolean("is_deleted").notNull().default(false),
+
+ // 프로젝트 PQ 사용 기한 (선택)
+ validTo: timestamp("valid_to"),
+ // 생성자
+ createdBy: integer("created_by").references(() => users.id),
+ // 수정자
+ updatedBy: integer("updated_by").references(() => users.id),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
+});
+
+export type PqList = typeof pqLists.$inferSelect;
+
export const pqCriterias = pgTable("pq_criterias", {
id: serial("id").primaryKey(),
code: varchar("code", { length: 50 }).notNull(), // 예: "1-1"
@@ -16,37 +48,26 @@ export const pqCriterias = pgTable("pq_criterias", {
// (선택) "GENERAL", "Quality Management System" 등 큰 분류
groupName: varchar("group_name", { length: 255 }),
- createdAt: timestamp("created_at").defaultNow().notNull(),
- updatedAt: timestamp("updated_at").defaultNow().notNull(),
-});
-
-export const pqCriteriasExtension = pgTable("pq_criterias_extension", {
- id: serial("id").primaryKey(),
+ subGroupName: varchar("sub_group_name", { length: 255 }),
- // pq_criterias와 연결 (1:1 or 1:N 중 필요에 맞춰 사용)
- pqCriteriaId: integer("pq_criteria_id")
+ // 해당 항목이 속하는 PQ 목록 (General/Project/Non-Inspection)
+ pqListId: integer("pq_list_id")
.notNull()
- .references(() => pqCriterias.id, {
+ .references(() => pqLists.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
- // projects 테이블에 대한 FK
- projectId: integer("project_id")
+ // 협력업체 입력 형식 – TEXT, FILE, EMAIL, PHONE, NUMBER 등
+ inputFormat: varchar("input_format", { length: 50 })
.notNull()
- .references(() => projects.id, {
- onDelete: "cascade",
- onUpdate: "cascade",
- }),
-
- // 프로젝트별 PQ 시 필요한 추가 정보
- contractInfo: text("contract_info"),
- additionalRequirement: text("additional_requirement"),
+ .default("TEXT"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
+
export const vendorCriteriaAttachments = pgTable("vendor_criteria_attachments", {
id: serial("id").primaryKey(),
vendorCriteriaAnswerId: integer("vendor_criteria_answer_id")
@@ -54,6 +75,7 @@ export const vendorCriteriaAttachments = pgTable("vendor_criteria_attachments",
.notNull(),
fileName: varchar("file_name", { length: 255 }).notNull(),
+ originalFileName: varchar("original_file_name", { length: 255 }),
filePath: varchar("file_path", { length: 1024 }).notNull(),
fileType: varchar("file_type", { length: 50 }),
fileSize: integer("file_size"),
@@ -82,38 +104,6 @@ export type PqCriterias = typeof pqCriterias.$inferSelect
// 협력업체와 프로젝트 PQ 요청 연결 테이블
-export const vendorProjectPQs = pgTable("vendor_project_pqs", {
- id: serial("id").primaryKey(),
-
- // vendors 테이블 FK
- vendorId: integer("vendor_id")
- .notNull()
- .references(() => vendors.id, {
- onDelete: "cascade",
- onUpdate: "cascade",
- }),
-
- // projects 테이블 FK
- projectId: integer("project_id")
- .notNull()
- .references(() => projects.id, {
- onDelete: "cascade",
- onUpdate: "cascade",
- }),
-
- // 상태: REQUESTED(요청됨), IN_PROGRESS(진행중), SUBMITTED(제출됨), APPROVED(승인됨), REJECTED(거부됨)
- status: varchar("status", { length: 20 }).notNull().default("REQUESTED"),
-
- // 메타데이터
- submittedAt: timestamp("submitted_at"),
- approvedAt: timestamp("approved_at"),
- rejectedAt: timestamp("rejected_at"),
- rejectReason: text("reject_reason"),
-
- createdAt: timestamp("created_at").defaultNow().notNull(),
- updatedAt: timestamp("updated_at").defaultNow().notNull(),
-});
-
export const vendorPQSubmissions = pgTable("vendor_pq_submissions", {
id: serial("id").primaryKey(),
@@ -137,9 +127,14 @@ export const vendorPQSubmissions = pgTable("vendor_pq_submissions", {
}),
// PQ 유형 구분을 명시적으로
- type: varchar("type", { length: 20 }).notNull(), // "GENERAL" or "PROJECT"
+ type: varchar("type", { length: 20 }).notNull(), // "GENERAL" or "PROJECT" or "NON_INSPECTION"
status: varchar("status", { length: 20 }).notNull().default("REQUESTED"),
+ dueDate: timestamp("due_date"),
+ agreements: jsonb("agreements").notNull().default({}), // ✅ 체크 항목들을 JSON으로 저장
+
+ // PQ 대상품목
+ pqItems: varchar("pq_items", { length: 1000 }),
submittedAt: timestamp("submitted_at"),
approvedAt: timestamp("approved_at"),
@@ -185,6 +180,8 @@ export const vendorPqCriteriaAnswers = pgTable("vendor_pq_criteria_answers", {
}),
answer: text("answer"),
+ shiComment: text("shi_comment"),
+ vendorReply: text("vendor_reply"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
@@ -259,6 +256,7 @@ export const vendorInvestigations = pgTable("vendor_investigations", {
"IN_PROGRESS", // 진행 중
"COMPLETED", // 완료됨
"CANCELED", // 취소됨
+ "RESULT_SENT", // 실사결과발송 - 구매담당자가 Vendor측으로 실사결과를 발송한 상태
],
})
.notNull()
@@ -268,8 +266,10 @@ export const vendorInvestigations = pgTable("vendor_investigations", {
evaluationType: varchar("evaluation_type", {
length: 50,
enum: [
- "SITE_AUDIT", // 실사의뢰평가
- "QM_SELF_AUDIT", // QM자체평가
+ "PURCHASE_SELF_EVAL", // 구매자체평가 - 신규 평가
+ "DOCUMENT_EVAL", // 서류평가 - 기존 QM 자체평가
+ "PRODUCT_INSPECTION", // 제품검사평가 - 기존 실사의뢰평가
+ "SITE_VISIT_EVAL" // 방문실사평가 - 기존 실사의뢰평가
],
}),
@@ -277,7 +277,15 @@ export const vendorInvestigations = pgTable("vendor_investigations", {
investigationAddress: text("investigation_address"),
// 실사 방법
- investigationMethod: varchar("investigation_method", { length: 100 }),
+ investigationMethod: varchar("investigation_method", {
+ length: 100,
+ enum: [
+ "PURCHASE_SELF_EVAL", // 구매자체평가
+ "DOCUMENT_EVAL", // 서류평가
+ "PRODUCT_INSPECTION", // 제품검사평가
+ "SITE_VISIT_EVAL" // 방문실사평가
+ ],
+ }),
// 실사 일정 시작일 / 종료일
scheduledStartAt: timestamp("scheduled_start_at"),
@@ -312,6 +320,9 @@ export const vendorInvestigations = pgTable("vendor_investigations", {
// 실사 내용이나 특이사항
investigationNotes: text("investigation_notes"),
+ // 구매 담당자 추가 의견 (실사 결과 발송 시 사용)
+ purchaseComment: text("purchase_comment"),
+
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
@@ -329,6 +340,7 @@ export const vendorInvestigationAttachments = pgTable(
.references(() => vendorInvestigations.id, { onDelete: "cascade" }),
fileName: varchar("file_name", { length: 255 }).notNull(),
+ originalFileName: varchar("original_file_name", { length: 255 }),
filePath: varchar("file_path", { length: 1024 }).notNull(),
// 권장: 사용자 경험과 기능성을 위해 추가
@@ -436,4 +448,114 @@ export const vendorInvestigationsView = pgView(
sql`users AS qm_manager`,
eq(vendorInvestigations.qmManagerId, sql`qm_manager.id`)
)
-}) \ No newline at end of file
+})
+
+// 방문실사 요청 테이블
+export const siteVisitRequests = pgTable("site_visit_requests", {
+ id: serial("id").primaryKey(),
+
+ // 어떤 실사(investigation)에 대한 방문실사 요청인지
+ investigationId: integer("investigation_id")
+ .notNull()
+ .references(() => vendorInvestigations.id, { onDelete: "cascade" }),
+
+ // 요청자
+ requesterId: integer("requester_id")
+ .references(() => users.id),
+
+ // 실사 기간 (W/D 기준)
+ inspectionDuration: decimal("inspection_duration", { precision: 4, scale: 1 }),
+
+ // 실사 요청일 (시작일, 종료일)
+ requestedStartDate: timestamp("requested_start_date"),
+ requestedEndDate: timestamp("requested_end_date"),
+
+ // SHI 실사참석 예정부문 (JSON으로 저장)
+ shiAttendees: jsonb("shi_attendees").notNull().default({}),
+
+ // 협력업체 요청정보 및 자료 (JSON으로 저장)
+ vendorRequests: jsonb("vendor_requests").notNull().default({}),
+
+ // 추가 요청사항
+ additionalRequests: text("additional_requests"),
+
+ // 상태
+ status: varchar("status", { length: 20 }).notNull().default("REQUESTED"),
+
+ // 메일 발송일
+ sentAt: timestamp("sent_at"),
+
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
+});
+
+// 방문실사 요청 첨부파일
+export const siteVisitRequestAttachments = pgTable("site_visit_request_attachments", {
+ id: serial("id").primaryKey(),
+
+ siteVisitRequestId: integer("site_visit_request_id")
+ .notNull()
+ .references(() => siteVisitRequests.id, { onDelete: "cascade" }),
+
+ // 협력업체가 제출한 첨부파일인 경우 vendor_site_visit_info와 연결
+ vendorSiteVisitInfoId: integer("vendor_site_visit_info_id")
+ .references(() => vendorSiteVisitInfo.id, { onDelete: "cascade" }),
+
+ fileName: varchar("file_name", { length: 255 }).notNull(),
+ originalFileName: varchar("original_file_name", { length: 255 }),
+ filePath: varchar("file_path", { length: 1024 }).notNull(),
+ fileSize: integer("file_size"),
+ mimeType: varchar("mime_type", { length: 100 }),
+
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
+});
+
+// 협력업체 방문실사 정보 테이블
+export const vendorSiteVisitInfo = pgTable("vendor_site_visit_info", {
+ id: serial("id").primaryKey(),
+
+ // 방문실사 요청과 연결
+ siteVisitRequestId: integer("site_visit_request_id")
+ .notNull()
+ .references(() => siteVisitRequests.id, { onDelete: "cascade" }),
+
+ // 공장 정보
+ factoryName: varchar("factory_name", { length: 255 }).notNull(),
+ factoryLocation: varchar("factory_location", { length: 255 }).notNull(),
+ factoryAddress: text("factory_address").notNull(),
+
+ // 담당자 정보
+ factoryPicName: varchar("factory_pic_name", { length: 255 }).notNull(),
+ factoryPicPhone: varchar("factory_pic_phone", { length: 50 }).notNull(),
+ factoryPicEmail: varchar("factory_pic_email", { length: 255 }).notNull(),
+
+ // 방문 정보
+ factoryDirections: text("factory_directions"),
+ accessProcedure: text("access_procedure"),
+
+
+
+ // 첨부파일 여부
+ hasAttachments: boolean("has_attachments").notNull().default(false),
+
+ // 기타 정보
+ otherInfo: text("other_info"),
+
+ // 제출 정보
+ submittedAt: timestamp("submitted_at").defaultNow().notNull(),
+ submittedBy: integer("submitted_by")
+ .notNull()
+ .references(() => users.id),
+
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
+});
+
+// 타입 정의
+export type SiteVisitRequest = typeof siteVisitRequests.$inferSelect;
+export type NewSiteVisitRequest = typeof siteVisitRequests.$inferInsert;
+export type SiteVisitRequestAttachment = typeof siteVisitRequestAttachments.$inferSelect;
+export type NewSiteVisitRequestAttachment = typeof siteVisitRequestAttachments.$inferInsert;
+export type VendorSiteVisitInfo = typeof vendorSiteVisitInfo.$inferSelect;
+export type NewVendorSiteVisitInfo = typeof vendorSiteVisitInfo.$inferInsert; \ No newline at end of file