summaryrefslogtreecommitdiff
path: root/db/seeds
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
committerjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
commit1a2241c40e10193c5ff7008a7b7b36cc1d855d96 (patch)
tree8a5587f10ca55b162d7e3254cb088b323a34c41b /db/seeds
initial commit
Diffstat (limited to 'db/seeds')
-rw-r--r--db/seeds/companies.ts21
-rw-r--r--db/seeds/companyseed.ts35
-rw-r--r--db/seeds/contract.ts197
-rw-r--r--db/seeds/itmeSeed.ts40
-rw-r--r--db/seeds/projects.ts43
-rw-r--r--db/seeds/rfqSeed.ts147
-rw-r--r--db/seeds/seedDcoments.ts57
-rw-r--r--db/seeds/seedIssueStage.ts76
-rw-r--r--db/seeds/seedRevision.ts173
-rw-r--r--db/seeds/seedRevisionSHI.ts162
-rw-r--r--db/seeds/tasks.ts21
-rw-r--r--db/seeds/taskseed.ts43
-rw-r--r--db/seeds/users.ts21
-rw-r--r--db/seeds/userseed.ts36
-rw-r--r--db/seeds/vendorDocu.ts144
-rw-r--r--db/seeds/vendorSeed.ts86
16 files changed, 1302 insertions, 0 deletions
diff --git a/db/seeds/companies.ts b/db/seeds/companies.ts
new file mode 100644
index 00000000..2bd437f3
--- /dev/null
+++ b/db/seeds/companies.ts
@@ -0,0 +1,21 @@
+import { seedCompanies } from "./companyseed"
+
+async function runSeed() {
+ console.log("⏳ Running seed...")
+
+ const start = Date.now()
+
+ await seedCompanies({ count: 100 })
+
+ const end = Date.now()
+
+ console.log(`✅ Seed completed in ${end - start}ms`)
+
+ process.exit(0)
+}
+
+runSeed().catch((err) => {
+ console.error("❌ Seed failed")
+ console.error(err)
+ process.exit(1)
+})
diff --git a/db/seeds/companyseed.ts b/db/seeds/companyseed.ts
new file mode 100644
index 00000000..b9d477f5
--- /dev/null
+++ b/db/seeds/companyseed.ts
@@ -0,0 +1,35 @@
+import db from "@/db/db"
+import { companies } from "@/db/schema/companies"
+import { faker } from "@faker-js/faker"
+export type NewCompany = typeof companies.$inferInsert
+
+
+function generateRandomCompany(): NewCompany {
+ return {
+ // Drizzle에서 기본 키(id)는 자동 생성이므로 제외
+ name: faker.company.name(),
+ taxID: faker.number.int({ min: 100000000, max: 999999999 }),
+ // createdAt은 defaultNow()가 있지만, 예시로 faker를 써서 과거 시점을 넣고 싶다면:
+ createdAt: faker.date.past()
+ }
+}
+
+export async function seedCompanies(input: { count: number }) {
+ const count = input.count ?? 100
+
+ try {
+ const allCompanies: NewCompany[] = []
+
+ for (let i = 0; i < count; i++) {
+ allCompanies.push(generateRandomCompany())
+ }
+
+ await db.delete(companies)
+
+ console.log("📝 Inserting companies", allCompanies.length)
+
+ await db.insert(companies).values(allCompanies).onConflictDoNothing()
+ } catch (err) {
+ console.error(err)
+ }
+}
diff --git a/db/seeds/contract.ts b/db/seeds/contract.ts
new file mode 100644
index 00000000..1c266c72
--- /dev/null
+++ b/db/seeds/contract.ts
@@ -0,0 +1,197 @@
+import db from "@/db/db"
+import { contracts, contractItems } from "@/db/schema/contract" // 실제 경로
+import { vendors } from "@/db/schema/vendors" // 실제 경로
+import { items } from "@/db/schema/items" // 실제 경로
+import { projects } from "@/db/schema/projects" // 실제 경로
+import { faker } from "@faker-js/faker"
+import { eq } from "drizzle-orm"
+
+async function main() {
+ console.log("Seeding contracts / contract_items...")
+
+ // (옵션) 기존 데이터 삭제
+ await db.delete(contractItems)
+ await db.delete(contracts)
+
+ // 1) 기존 vendor, item, project 목록 조회
+ const allVendors = await db.select().from(vendors)
+ const allItems = await db.select().from(items)
+ const allProjects = await db.select().from(projects)
+
+ if (allVendors.length === 0 || allItems.length === 0 || allProjects.length === 0) {
+ console.log("Vendors / Items / Projects 테이블 중 하나가 비어있습니다. 먼저 해당 테이블을 seed 해주세요.")
+ process.exit(1)
+ }
+
+ // -- 조선업 맥락에 맞는 예시 문구들 --
+ const shipyardProjectNamesKR = [
+ "벌크선 선체 건조",
+ "컨테이너선 엔진 오버홀",
+ "해양 플랜트 모듈 제작",
+ "LNG 운반선 화물창 시공",
+ "FPSO 개조 작업",
+ "밸러스트 수 처리장치 설치",
+ "VLCC 프로펠러 교체",
+ "드릴십 탑사이드 모듈 제작",
+ ]
+ const paymentTermsExamples = [
+ "선금 30%, 중도금 20%, 잔금 50% (인도 후 60일)",
+ "LC 결제 (신용장 발행 후 30일)",
+ "전액 선결제",
+ "계약금 10%, 선박 명명식 시 40%, 인도 시 50%",
+ ]
+ const deliveryTermsExamples = [
+ "FOB 부산항",
+ "CIF 상하이항",
+ "DAP 울산조선소",
+ "DDP 거제 옥포조선소",
+ ]
+ const deliveryLocations = [
+ "부산 영도조선소",
+ "울산 본사 도크 #3",
+ "거제 옥포조선소 해양공장",
+ "목포신항 부두",
+ ]
+
+ // (여기서는 30개) 계약 생성
+ const NUM_CONTRACTS = 30
+
+ for (let i = 0; i < NUM_CONTRACTS; i++) {
+ // 무작위 벤더, 프로젝트 선택
+ const randomVendor = faker.helpers.arrayElement(allVendors)
+ const randomProject = faker.helpers.arrayElement(allProjects)
+
+ // 예: C-1234
+ const contractNo = `C-${faker.number.int({ min: 1000, max: 9999 })}`
+
+ // 예: "조선 프로젝트: 벌크선 선체 건조 - (프로젝트명)"
+ const randomShipyardName = faker.helpers.arrayElement(shipyardProjectNamesKR)
+ const contractName = `조선 프로젝트: ${randomShipyardName} - ${randomProject.name}`
+
+ // 상태
+ const randomStatus = faker.helpers.arrayElement([
+ "ACTIVE",
+ "FINISHED",
+ "CANCELED",
+ "ON_HOLD",
+ ])
+
+ // 날짜
+ const startDate = faker.helpers.maybe(() => faker.date.past({ years: 1 }), {
+ probability: 0.7,
+ })
+ const endDate = faker.helpers.maybe(() => faker.date.future({ years: 0.5 }), {
+ probability: 0.5,
+ })
+
+ // PO에 추가된 필드들에 대해 랜덤 할당
+ const paymentTerms = faker.helpers.arrayElement(paymentTermsExamples)
+ const deliveryTerms = faker.helpers.arrayElement(deliveryTermsExamples)
+ const deliveryDate = faker.date.future({ years: 1 })
+ const deliveryLocation = faker.helpers.arrayElement(deliveryLocations)
+
+ // 가격 관련 (단순 랜덤)
+ const totalAmount = faker.number.float({ min: 5000000, max: 500000000 })
+ const discount = faker.number.float({ min: 0, max: 500000})
+ const tax = faker.number.float({ min: 0, max: 1000000 })
+ const shippingFee = faker.number.float({ min: 0, max: 300000})
+ const netTotal = totalAmount - discount + tax + shippingFee
+
+ // 부분 납품/결제 가능 여부
+ const partialShippingAllowed = faker.datatype.boolean()
+ const partialPaymentAllowed = faker.datatype.boolean()
+
+ // remarks, version
+ const remarks = faker.helpers.maybe(() => faker.lorem.sentence(), {
+ probability: 0.4,
+ })
+ const version = 1 // 혹은 faker.number.int({ min: 1, max: 5 })
+
+ // DB에 insert
+ const [insertedContract] = await db
+ .insert(contracts)
+ .values({
+ projectId: randomProject.id,
+ vendorId: randomVendor.id,
+ contractNo,
+ contractName,
+ status: randomStatus,
+ startDate: startDate ?? null,
+ endDate: endDate ?? null,
+
+ paymentTerms,
+ deliveryTerms,
+ deliveryDate,
+ deliveryLocation,
+
+ currency: faker.helpers.arrayElement(["KRW", "USD"]),
+ totalAmount,
+ discount,
+ tax,
+ shippingFee,
+ netTotal,
+
+ partialShippingAllowed,
+ partialPaymentAllowed,
+
+ remarks: remarks ?? null,
+ version,
+ })
+ .returning({ id: contracts.id })
+
+ const contractId = insertedContract.id
+ console.log(`Inserted Contract #${contractId}: ${contractNo}`)
+
+ // 3) 각 계약에 대해 2~5개 contract_items
+ const numItems = faker.number.int({ min: 2, max: 5 })
+ const usedItemIds = new Set<number>()
+
+ for (let j = 0; j < numItems; j++) {
+ // 중복되지 않게 아이템 추출
+ let randomItem
+ do {
+ randomItem = faker.helpers.arrayElement(allItems)
+ } while (usedItemIds.has(randomItem.id))
+ usedItemIds.add(randomItem.id)
+
+ const quantity = faker.number.int({ min: 1, max: 100 })
+ // unitPrice: 1천원 ~ 50만원 범위 (소수점 2자리)
+ const unitPrice = faker.number.float({ min: 1000, max: 500000 })
+
+ // 세율, 세금, 합계 계산 (예시)
+ const taxRate = faker.helpers.arrayElement([0, 0.05, 0.1])
+ const taxAmount = parseFloat((unitPrice * quantity * taxRate).toFixed(2))
+ const totalLineAmount = parseFloat((unitPrice * quantity + taxAmount).toFixed(2))
+
+ const description = faker.commerce.productName() // 간단히 예시
+ const remark = faker.helpers.maybe(() => faker.lorem.sentence(), { probability: 0.3 })
+
+ const [insertedContractItem] = await db
+ .insert(contractItems)
+ .values({
+ contractId,
+ itemId: randomItem.id,
+ description,
+ quantity,
+ unitPrice,
+ taxRate,
+ taxAmount,
+ totalLineAmount,
+ remark: remark ?? null,
+ })
+ .returning({ id: contractItems.id })
+
+ console.log(
+ ` -> Inserted ContractItem #${insertedContractItem.id} (itemId=${randomItem.id})`
+ )
+ }
+ }
+
+ console.log("Seeding completed.")
+ process.exit(0)
+}
+
+main().catch((err) => {
+ console.error(err)
+ process.exit(1)
+}) \ No newline at end of file
diff --git a/db/seeds/itmeSeed.ts b/db/seeds/itmeSeed.ts
new file mode 100644
index 00000000..66d4cbfd
--- /dev/null
+++ b/db/seeds/itmeSeed.ts
@@ -0,0 +1,40 @@
+// db/seed/itemSeed.ts
+import { faker } from "@faker-js/faker"
+import db from "@/db/db"
+import { items } from "../schema/items"
+import { eq } from "drizzle-orm"
+
+export async function seedItems() {
+ // 1) 기존 데이터 삭제 (원하면)
+ await db.delete(items)
+
+ // 2) 임의로 10개의 아이템 생성 (원하는 개수로 조정)
+ const itemCount = 10
+
+ for (let i = 0; i < itemCount; i++) {
+ const newItem = {
+ // itemCode: PK. 예: "ITEM-AB12"
+ itemCode: faker.string.alpha({ length: 6 }).toUpperCase(),
+ itemName: faker.commerce.productName(),
+ description: faker.commerce.productDescription(),
+ // createdAt, updatedAt 은 DB defaultNow() 사용
+ }
+
+ await db.insert(items).values(newItem)
+ }
+
+ console.log(`✅ Seeded ${itemCount} items.`)
+}
+
+// 단독 실행 가능하도록 아래처럼 작성
+if (require.main === module) {
+ seedItems()
+ .then(() => {
+ console.log("Item seeding complete!")
+ process.exit(0)
+ })
+ .catch((error) => {
+ console.error("Item seeding failed:", error)
+ process.exit(1)
+ })
+} \ No newline at end of file
diff --git a/db/seeds/projects.ts b/db/seeds/projects.ts
new file mode 100644
index 00000000..64bf3173
--- /dev/null
+++ b/db/seeds/projects.ts
@@ -0,0 +1,43 @@
+// /scripts/seedProjects.ts (예시 경로)
+
+import db from "@/db/db"
+import { projects } from "@/db/schema/projects"
+import { faker } from "@faker-js/faker"
+import { eq } from "drizzle-orm"
+
+async function main() {
+ console.log("Seeding projects...")
+
+ // (선택) 기존 데이터 삭제
+ await db.delete(projects)
+
+ // N개의 프로젝트 생성
+ const NUM_PROJECTS = 10
+
+ for (let i = 0; i < NUM_PROJECTS; i++) {
+ // 무작위 프로젝트 코드 (ex: "P-1234")
+ const projectCode = `P-${faker.number.int({ min: 1000, max: 9999 })}`
+ // 무작위 프로젝트 이름 (ex: "Project" + adjective/noun)
+ const projectName = `Project ${faker.word.adjective()} ${faker.word.noun()}`
+
+ // Insert
+ const [inserted] = await db
+ .insert(projects)
+ .values({
+ code: projectCode,
+ name: projectName,
+ })
+ .returning({ id: projects.id })
+
+ console.log(`Inserted Project #${inserted.id}: ${projectCode} - ${projectName}`)
+ }
+
+ console.log("Seeding projects completed.")
+ process.exit(0)
+}
+
+// 실행
+main().catch((err) => {
+ console.error(err)
+ process.exit(1)
+}) \ No newline at end of file
diff --git a/db/seeds/rfqSeed.ts b/db/seeds/rfqSeed.ts
new file mode 100644
index 00000000..2d37393d
--- /dev/null
+++ b/db/seeds/rfqSeed.ts
@@ -0,0 +1,147 @@
+// db/seed/rfqSeed.ts
+import { faker } from "@faker-js/faker";
+import db from "@/db/db"
+import { eq, inArray } from "drizzle-orm";
+
+// 스키마 import
+import {
+ rfqs, rfqItems, rfqVendors, vendorQuotes, rfqAttachments, rfqComments, rfqEvaluations,
+ Rfq, RfqItem, RfqVendor, VendorQuote, RfqAttach, RfqComment, RfqEvaluation
+} from "../schema/rfq";
+import { vendors } from "../schema/vendors";
+
+export async function seedRfqData() {
+ // 1) 기존 데이터 삭제 (참조 순서 유의)
+ await db.delete(rfqEvaluations);
+ await db.delete(rfqComments);
+ await db.delete(rfqAttachments);
+ await db.delete(vendorQuotes);
+ await db.delete(rfqVendors);
+ await db.delete(rfqItems);
+ await db.delete(rfqs);
+
+ // (벤더 목록이 이미 존재한다고 가정)
+ // 혹은 필요한 경우 vendors도 시드할 수 있음
+ const existingVendors = await db.select().from(vendors);
+ if (existingVendors.length === 0) {
+ console.log("No vendors found. Please seed vendors first.");
+ return;
+ }
+
+ // 2) N개의 RFQ 생성
+ const rfqCount = 5;
+ const rfqIds: number[] = [];
+
+ for (let i = 0; i < rfqCount; i++) {
+ const newRfqData = {
+ rfqCode: `RFQ-${faker.string.alpha({ length: 4 }).toUpperCase()}-${faker.number.int({
+ min: 100,
+ max: 999,
+ })}`,
+ projectCode: `PRJ-${faker.number.int({ min: 1000, max: 9999 })}`,
+ projectName: faker.company.name(),
+ dueDate: faker.date.future(), // 임의 미래 날짜
+ status: faker.helpers.arrayElement(["DRAFT", "PUBLISHED", "EVALUATION", "AWARDED"]),
+ createdBy: faker.number.int({ min: 1, max: 10 }),
+ };
+
+ const [insertedRfq] = await db.insert(rfqs).values(newRfqData).returning();
+ rfqIds.push(insertedRfq.id);
+ }
+
+ // 3) For each RFQ, create items, vendors, quotes, attachments, comments, evaluations
+ for (const rfqId of rfqIds) {
+ // 3-1) RFQ Items
+ const itemCount = faker.number.int({ min: 1, max: 3 });
+ const rfqItemIds: number[] = [];
+ for (let j = 0; j < itemCount; j++) {
+ const newItem = {
+ rfqId,
+ itemCode: `ITEM-${faker.string.alphanumeric({ length: 5 }).toUpperCase()}`,
+ itemName: faker.commerce.productName(),
+ description: faker.commerce.productDescription(),
+ quantity: faker.number.float({ min: 1, max: 20, fractionDigits: 2 }),
+ uom: faker.helpers.arrayElement(["EA", "KG", "BOX"]),
+ };
+ const [insertedItem] = await db.insert(rfqItems).values(newItem).returning();
+ rfqItemIds.push(insertedItem.id);
+ }
+
+ // 3-2) RFQ Vendors
+ // - 랜덤으로 2~3개 벤더를 pick
+ const pickedVendors = faker.helpers.arrayElements(existingVendors, faker.number.int({ min: 2, max: 3 }));
+ for (const ven of pickedVendors) {
+ const newRfqVendor = {
+ rfqId,
+ vendorId: ven.id,
+ status: faker.helpers.arrayElement(["INVITED", "ACCEPTED", "REJECTED", "QUOTED"]),
+ };
+ const [insertedRfqVendor] = await db.insert(rfqVendors).values(newRfqVendor).returning();
+
+ // 3-3) 임의로 벤더 Quotes 생성 (50% 확률)
+ if (faker.datatype.boolean()) {
+ const newQuote = {
+ rfqId,
+ vendorId: ven.id,
+ totalAmount: faker.number.float({ min: 1000, max: 50000, fractionDigits: 2 }),
+ currency: faker.helpers.arrayElement(["USD", "EUR", "KRW"]),
+ leadTime: `${faker.number.int({ min: 10, max: 40 })} days`,
+ notes: faker.lorem.sentence(),
+ };
+ await db.insert(vendorQuotes).values(newQuote);
+ }
+ }
+
+ // 3-4) Attachments (랜덤 0~2개)
+ const attachCount = faker.number.int({ min: 0, max: 2 });
+ for (let a = 0; a < attachCount; a++) {
+ const newAttach = {
+ rfqId,
+ vendorId: null, // or pick some vendorId
+ fileName: faker.system.fileName(),
+ filePath: faker.system.filePath(),
+ // evaluationId: null,
+ };
+ await db.insert(rfqAttachments).values(newAttach);
+ }
+
+ // 3-5) Comments (랜덤 1~2개)
+ const commentCount = faker.number.int({ min: 1, max: 2 });
+ for (let c = 0; c < commentCount; c++) {
+ const newComment = {
+ rfqId,
+ vendorId: null,
+ commentText: faker.lorem.sentence(),
+ commentedBy: faker.number.int({ min: 1, max: 10 }),
+ // evaluationId: null,
+ };
+ await db.insert(rfqComments).values(newComment);
+ }
+
+ // 3-6) Evaluations (랜덤 0~1개)
+ if (faker.datatype.boolean()) {
+ const newEval = {
+ rfqId,
+ vendorId: faker.helpers.arrayElement(pickedVendors).id,
+ evalType: faker.helpers.arrayElement(["TBE", "CBE"]),
+ result: faker.helpers.arrayElement(["PASS", "FAIL", "ACCEPTABLE"]),
+ notes: faker.lorem.sentences(2),
+ };
+ await db.insert(rfqEvaluations).values(newEval);
+ }
+ }
+
+ console.log(`✅ Seeded ${rfqIds.length} RFQs (with items, vendors, quotes, attachments, comments, evaluations).`);
+}
+
+if (require.main === module) {
+ seedRfqData()
+ .then(() => {
+ console.log("RFQ seeding complete!");
+ process.exit(0);
+ })
+ .catch((err) => {
+ console.error("RFQ seeding failed:", err);
+ process.exit(1);
+ });
+} \ No newline at end of file
diff --git a/db/seeds/seedDcoments.ts b/db/seeds/seedDcoments.ts
new file mode 100644
index 00000000..5fb1beea
--- /dev/null
+++ b/db/seeds/seedDcoments.ts
@@ -0,0 +1,57 @@
+/**
+ * seedDocuments.ts
+ * - documents 테이블에 테스트 데이터 삽입
+ */
+
+import db from "@/db/db"
+import { faker } from "@faker-js/faker"
+import { documents } from "../schema/vendorDocu"
+
+// 몇 개의 도큐먼트를 만들지
+const NUM_DOCUMENTS = 200
+
+// 가상으로 계약 ID가 96~125 범위
+const CONTRACT_ID_MIN = 96
+const CONTRACT_ID_MAX = 125
+
+async function seedDocuments() {
+ try {
+ console.log("Seeding documents started...")
+
+ // documents 테이블에 NUM_DOCUMENTS 개 생성
+ const docsData = []
+ for (let i = 0; i < NUM_DOCUMENTS; i++) {
+ const randomContractId = faker.number.int({
+ min: CONTRACT_ID_MIN,
+ max: CONTRACT_ID_MAX,
+ })
+
+ docsData.push({
+ contractId: randomContractId,
+ docNumber: `DOC-${faker.number.int({ min: 1000, max: 9999 })}`,
+ title: faker.lorem.sentence(3),
+ status: faker.helpers.arrayElement(["ACTIVE", "REVIEW", "APPROVED"]),
+ issuedDate: faker.helpers.maybe(() => faker.date.past({ years: 1 }), {
+ probability: 0.7,
+ })
+ })
+ }
+
+ // documents 테이블 INSERT (returning 사용 안 함)
+ await db.insert(documents).values(docsData)
+
+ // // 삽입된 문서 수 확인
+ // const count = await db.select({ count: db.fn.count() }).from(documents)
+ // console.log(`Documents in database: ${count[0].count}`)
+
+ console.log("Seeding documents completed successfully.")
+ } catch (err) {
+ console.error("Seeding documents error:", err)
+ process.exit(1)
+ }
+}
+
+// 스크립트 직접 실행 시
+seedDocuments()
+ .then(() => process.exit(0))
+ .catch(() => process.exit(1)) \ No newline at end of file
diff --git a/db/seeds/seedIssueStage.ts b/db/seeds/seedIssueStage.ts
new file mode 100644
index 00000000..117e2a99
--- /dev/null
+++ b/db/seeds/seedIssueStage.ts
@@ -0,0 +1,76 @@
+/**
+ * seedIssueStages.ts
+ * - issueStages 테이블에 테스트 데이터 삽입
+ */
+
+import db from "@/db/db"
+import { faker } from "@faker-js/faker"
+import { documents, issueStages } from "../schema/vendorDocu"
+
+// 하나의 문서당 스테이지 개수 범위
+const STAGE_MIN = 1
+const STAGE_MAX = 3
+
+async function seedIssueStages() {
+ try {
+ console.log("Seeding issue stages started...")
+
+ // 먼저 documents 테이블에서 모든 문서 ID 가져오기
+ const allDocs = await db.select({ id: documents.id }).from(documents)
+ console.log(`Found ${allDocs.length} documents to create stages for.`)
+
+ if (allDocs.length === 0) {
+ console.warn("No documents found. Run seedDocuments.ts first.")
+ return
+ }
+
+ // 각 문서에 대해 스테이지 생성 및 삽입
+ let totalStagesCreated = 0
+
+ for (const doc of allDocs) {
+ const stageCount = faker.number.int({ min: STAGE_MIN, max: STAGE_MAX })
+ const stagesData = []
+
+ for (let j = 0; j < stageCount; j++) {
+ stagesData.push({
+ documentId: doc.id,
+ stageName: faker.helpers.arrayElement([
+ "Issued for Review",
+ "IFC",
+ "AFC",
+ "As-Built",
+ ]),
+ planDate: faker.helpers.maybe(() => faker.date.future({ years: 0.5 }), {
+ probability: 0.5,
+ }),
+ actualDate: faker.helpers.maybe(() => faker.date.future({ years: 0.5 }), {
+ probability: 0.5,
+ })
+ })
+ }
+
+ // 한 번에 많은 데이터를 삽입하지 않고 문서별로 삽입
+ try {
+ await db.insert(issueStages).values(stagesData)
+ totalStagesCreated += stagesData.length
+
+ // 진행 상황 로깅 (50개마다)
+ if (totalStagesCreated % 50 === 0) {
+ console.log(`Created ${totalStagesCreated} stages so far...`)
+ }
+ } catch (error) {
+ console.error(`Error inserting stages for document ${doc.id}:`, error)
+ }
+ }
+
+ console.log("Seeding issue stages completed successfully.")
+ } catch (err) {
+ console.error("Seeding issue stages error:", err)
+ process.exit(1)
+ }
+}
+
+// 스크립트 직접 실행 시
+seedIssueStages()
+ .then(() => process.exit(0))
+ .catch(() => process.exit(1)) \ No newline at end of file
diff --git a/db/seeds/seedRevision.ts b/db/seeds/seedRevision.ts
new file mode 100644
index 00000000..c41fa333
--- /dev/null
+++ b/db/seeds/seedRevision.ts
@@ -0,0 +1,173 @@
+/**
+ * seedVendorRevisions.ts
+ * - revisions 테이블에 vendor 업로더 타입의 테스트 데이터 삽입
+ * - issueStageId + revision 조합이 유니크해야 함
+ */
+
+import db from "@/db/db"
+import { faker } from "@faker-js/faker"
+import { issueStages, revisions } from "../schema/vendorDocu"
+import { eq } from "drizzle-orm"
+
+// 하나의 스테이지당 리비전 개수 범위
+const REV_MIN = 1
+const REV_MAX = 2
+
+// 가능한 리비전 코드 목록
+const POSSIBLE_REVISIONS = ["A", "B", "C", "D", "E", "0", "1", "2", "3", "4"];
+
+// 가능한 상태 목록 (vendor 타입에 적합한 상태들)
+const POSSIBLE_STATUSES = ["submitted", "in_review", "approved", "rejected"];
+
+async function seedVendorRevisions() {
+ try {
+ console.log("Seeding vendor revisions started...")
+
+ // 먼저 issueStages 테이블에서 모든 스테이지 정보 가져오기
+ const allStages = await db.select({
+ id: issueStages.id,
+ }).from(issueStages)
+
+ console.log(`Found ${allStages.length} stages to create vendor revisions for.`)
+
+ if (allStages.length === 0) {
+ console.warn("No stages found. Run seedIssueStages.ts first.")
+ return
+ }
+
+ // 각 스테이지에 대해 리비전 생성 및 삽입
+ let totalRevisionsCreated = 0
+
+ // 배치 처리를 위한 설정
+ const BATCH_SIZE = 50 // 작은 배치 사이즈로 조정 (유니크 제약조건 때문에)
+ let revisionBatch = []
+
+ // 각 스테이지 ID별로 이미 사용된 리비전 코드를 추적
+ const usedRevisions = new Map();
+
+ for (const stage of allStages) {
+ // 이 스테이지에 사용할 수 있는 리비전 코드들
+ const availableRevisions = [...POSSIBLE_REVISIONS];
+
+ // 이미 사용된 리비전 코드들 가져오기 (DB에 저장된 것 확인)
+ const existingRevs = await db.select({ revision: revisions.revision })
+ .from(revisions)
+ .where(eq(revisions.issueStageId, stage.id));
+
+ // 이미 사용된 리비전 코드 제외
+ existingRevs.forEach(rev => {
+ const index = availableRevisions.indexOf(rev.revision);
+ if (index !== -1) {
+ availableRevisions.splice(index, 1);
+ }
+ });
+
+ // 리비전 개수 계산 (가용 리비전 코드 수에 제한)
+ const revCount = Math.min(
+ faker.number.int({ min: REV_MIN, max: REV_MAX }),
+ availableRevisions.length
+ );
+
+ // 리비전 코드가 더 이상 없으면 스킵
+ if (revCount === 0) {
+ console.warn(`No available revision codes for stage ${stage.id}, skipping...`);
+ continue;
+ }
+
+ // 사용할 리비전 코드 무작위 선택
+ const selectedRevisions = faker.helpers.arrayElements(availableRevisions, revCount);
+
+ for (const revisionCode of selectedRevisions) {
+ // 랜덤 업체명 생성
+ const companyNames = ["ABC Engineering", "XYZ Construction", "Global Tech Solutions", "Mega Builders"];
+ const uploaderName = faker.helpers.maybe(() => {
+ const name = faker.person.fullName();
+ const company = faker.helpers.arrayElement(companyNames);
+ return `${name} (${company})`;
+ }, { probability: 0.7 });
+
+ // 랜덤 코멘트 생성
+ const comment = faker.helpers.maybe(() => {
+ return faker.helpers.arrayElement([
+ "문서 초안 업로드입니다.",
+ "수정 사항이 반영된 버전입니다.",
+ "최신 규격에 맞게 업데이트했습니다.",
+ "요청하신 변경사항 적용했습니다.",
+ "검토 후 피드백 부탁드립니다."
+ ]);
+ }, { probability: 0.6 });
+
+ // 상태 설정
+ const status = faker.helpers.arrayElement(POSSIBLE_STATUSES);
+
+ // 리비전 객체 생성
+ revisionBatch.push({
+ issueStageId: stage.id,
+ revision: revisionCode,
+ uploaderType: "vendor", // 업로더 타입을 vendor로 설정
+ uploaderName,
+ comment,
+ status,
+ approvedDate: status === "approved" ? faker.date.recent({ days: 10 }) : null,
+ createdAt: faker.date.recent({ days: 30 }),
+ updatedAt: new Date(),
+ });
+
+ // 배치 크기에 도달하면 DB에 삽입
+ if (revisionBatch.length >= BATCH_SIZE) {
+ try {
+ await db.insert(revisions).values(revisionBatch);
+ totalRevisionsCreated += revisionBatch.length;
+ console.log(`Created ${totalRevisionsCreated} vendor revisions so far...`);
+ revisionBatch = []; // 배치 초기화
+ } catch (error) {
+ console.error(`Error inserting revision batch:`, error);
+
+ // 유니크 제약조건 충돌 시 개별 삽입 시도
+ console.log("Trying to insert revisions one by one...");
+ for (const rev of revisionBatch) {
+ try {
+ await db.insert(revisions).values([rev]);
+ totalRevisionsCreated++;
+ } catch (singleError) {
+ console.warn(`Skipping duplicate revision: Stage ${rev.issueStageId}, Rev ${rev.revision}`);
+ }
+ }
+ revisionBatch = [];
+ }
+ }
+ }
+ }
+
+ // 마지막 배치 처리
+ if (revisionBatch.length > 0) {
+ try {
+ await db.insert(revisions).values(revisionBatch);
+ totalRevisionsCreated += revisionBatch.length;
+ } catch (error) {
+ console.error(`Error inserting final revision batch:`, error);
+
+ // 유니크 제약조건 충돌 시 개별 삽입 시도
+ console.log("Trying to insert remaining revisions one by one...");
+ for (const rev of revisionBatch) {
+ try {
+ await db.insert(revisions).values([rev]);
+ totalRevisionsCreated++;
+ } catch (singleError) {
+ console.warn(`Skipping duplicate revision: Stage ${rev.issueStageId}, Rev ${rev.revision}`);
+ }
+ }
+ }
+ }
+
+ console.log(`Seeding vendor revisions completed successfully. Total created: ${totalRevisionsCreated}`);
+ } catch (err) {
+ console.error("Seeding vendor revisions error:", err);
+ process.exit(1);
+ }
+}
+
+// 스크립트 직접 실행 시
+seedVendorRevisions()
+ .then(() => process.exit(0))
+ .catch(() => process.exit(1)); \ No newline at end of file
diff --git a/db/seeds/seedRevisionSHI.ts b/db/seeds/seedRevisionSHI.ts
new file mode 100644
index 00000000..563878ce
--- /dev/null
+++ b/db/seeds/seedRevisionSHI.ts
@@ -0,0 +1,162 @@
+/**
+ * seedSHIRevisions.ts
+ * - 기존 vendor 업로더 타입 리비전 중 일부를 선택하여
+ * - 동일한 stage와 revision 값을 가지지만 shi 업로더 타입인 리비전을 생성
+ */
+
+import db from "@/db/db"
+import { faker } from "@faker-js/faker"
+import { issueStages, revisions } from "../schema/vendorDocu"
+import { and, eq, ne } from "drizzle-orm"
+
+// SHI 리비전으로 변환할 vendor 리비전의 비율 (0.0 ~ 1.0)
+const SHI_CONVERSION_RATIO = 0.3;
+
+// 가능한 SHI 상태 목록
+const SHI_STATUSES = ["official", "approved", "registered"];
+
+// SHI 담당자 목록
+const SHI_HANDLERS = [
+ "김영수 (삼성중공업)",
+ "이지원 (삼성중공업)",
+ "박민호 (삼성중공업)",
+ "정수진 (SHI)",
+ "윤태준 (SHI)"
+];
+
+// SHI 코멘트 목록
+const SHI_COMMENTS = [
+ "공식 문서로 등록합니다.",
+ "검토 완료된 문서입니다.",
+ "SHI 표준에 맞게 수정되었습니다.",
+ "등록 완료된 문서입니다.",
+ "최종 승인 문서입니다."
+];
+
+async function seedSHIRevisions() {
+ try {
+ console.log("Seeding SHI revisions started...")
+
+ // 이미 존재하는 vendor 업로더 타입의 리비전 가져오기
+ const vendorRevisions = await db
+ .select({
+ id: revisions.id,
+ issueStageId: revisions.issueStageId,
+ revision: revisions.revision
+ })
+ .from(revisions)
+ .where(eq(revisions.uploaderType, "vendor"));
+
+ console.log(`Found ${vendorRevisions.length} vendor revisions to potentially convert to SHI.`);
+
+ if (vendorRevisions.length === 0) {
+ console.warn("No vendor revisions found. Run seedVendorRevisions.ts first.");
+ return;
+ }
+
+ // SHI 리비전으로 변환할 vendor 리비전 선택
+ const revisionsToConvert = faker.helpers.arrayElements(
+ vendorRevisions,
+ Math.floor(vendorRevisions.length * SHI_CONVERSION_RATIO)
+ );
+
+ console.log(`Selected ${revisionsToConvert.length} revisions to convert to SHI.`);
+
+ // 이미 SHI 리비전이 존재하는지 확인하기 위한 Set
+ const existingSHIRevisions = new Set();
+ const shiRevs = await db
+ .select({
+ issueStageId: revisions.issueStageId,
+ revision: revisions.revision
+ })
+ .from(revisions)
+ .where(eq(revisions.uploaderType, "shi"));
+
+ shiRevs.forEach(rev => {
+ existingSHIRevisions.add(`${rev.issueStageId}-${rev.revision}`);
+ });
+
+ // 배치 처리를 위한 설정
+ const BATCH_SIZE = 50;
+ let shiBatch = [];
+ let totalSHICreated = 0;
+
+ // 각 vendor 리비전에 대해 SHI 리비전 생성
+ for (const vRev of revisionsToConvert) {
+ // 이미 SHI 리비전이 존재하는 경우 스킵
+ const key = `${vRev.issueStageId}-${vRev.revision}`;
+ if (existingSHIRevisions.has(key)) {
+ console.log(`Skipping existing SHI revision: ${key}`);
+ continue;
+ }
+
+ // SHI 리비전 객체 생성
+ shiBatch.push({
+ issueStageId: vRev.issueStageId,
+ revision: vRev.revision,
+ uploaderType: "shi", // 업로더 타입을 shi로 설정
+ uploaderName: faker.helpers.arrayElement(SHI_HANDLERS),
+ comment: faker.helpers.maybe(() => faker.helpers.arrayElement(SHI_COMMENTS), { probability: 0.7 }),
+ status: faker.helpers.arrayElement(SHI_STATUSES),
+ approvedDate: faker.helpers.maybe(() => faker.date.recent({ days: 5 }), { probability: 0.8 }),
+ createdAt: faker.date.recent({ days: 20 }),
+ updatedAt: new Date(),
+ });
+
+ // 배치 크기에 도달하면 DB에 삽입
+ if (shiBatch.length >= BATCH_SIZE) {
+ try {
+ await db.insert(revisions).values(shiBatch);
+ totalSHICreated += shiBatch.length;
+ console.log(`Created ${totalSHICreated} SHI revisions so far...`);
+ shiBatch = []; // 배치 초기화
+ } catch (error) {
+ console.error(`Error inserting SHI revision batch:`, error);
+
+ // 유니크 제약조건 충돌 시 개별 삽입 시도
+ console.log("Trying to insert SHI revisions one by one...");
+ for (const rev of shiBatch) {
+ try {
+ await db.insert(revisions).values([rev]);
+ totalSHICreated++;
+ } catch (singleError) {
+ console.warn(`Skipping duplicate SHI revision: Stage ${rev.issueStageId}, Rev ${rev.revision}`);
+ }
+ }
+ shiBatch = [];
+ }
+ }
+ }
+
+ // 마지막 배치 처리
+ if (shiBatch.length > 0) {
+ try {
+ await db.insert(revisions).values(shiBatch);
+ totalSHICreated += shiBatch.length;
+ } catch (error) {
+ console.error(`Error inserting final SHI revision batch:`, error);
+
+ // 유니크 제약조건 충돌 시 개별 삽입 시도
+ console.log("Trying to insert remaining SHI revisions one by one...");
+ for (const rev of shiBatch) {
+ try {
+ await db.insert(revisions).values([rev]);
+ totalSHICreated++;
+ } catch (singleError) {
+ console.warn(`Skipping duplicate SHI revision: Stage ${rev.issueStageId}, Rev ${rev.revision}`);
+ }
+ }
+ }
+ }
+
+ console.log(`Seeding SHI revisions completed successfully. Total created: ${totalSHICreated}`);
+ } catch (err) {
+ console.error("Seeding SHI revisions error:", err);
+ process.exit(1);
+ }
+}
+
+// 스크립트 직접 실행 시
+seedSHIRevisions()
+ .then(() => process.exit(0))
+ .catch(() => process.exit(1)); \ No newline at end of file
diff --git a/db/seeds/tasks.ts b/db/seeds/tasks.ts
new file mode 100644
index 00000000..ac1ba337
--- /dev/null
+++ b/db/seeds/tasks.ts
@@ -0,0 +1,21 @@
+import { seedTasks } from "./taskseed"
+
+async function runSeed() {
+ console.log("⏳ Running seed...")
+
+ const start = Date.now()
+
+ await seedTasks({ count: 100 })
+
+ const end = Date.now()
+
+ console.log(`✅ Seed completed in ${end - start}ms`)
+
+ process.exit(0)
+}
+
+runSeed().catch((err) => {
+ console.error("❌ Seed failed")
+ console.error(err)
+ process.exit(1)
+})
diff --git a/db/seeds/taskseed.ts b/db/seeds/taskseed.ts
new file mode 100644
index 00000000..e58b29c2
--- /dev/null
+++ b/db/seeds/taskseed.ts
@@ -0,0 +1,43 @@
+import db from "@/db/db"
+import { tasks } from "@/db/schema/tasks"
+import { generateId } from "@/lib/id"
+import { faker } from "@faker-js/faker"
+export type NewTask = typeof tasks.$inferInsert
+
+
+function generateRandomTask(): NewTask {
+ return {
+ id: generateId("task"),
+ code: undefined,
+ title: faker.hacker
+ .phrase()
+ .replace(/^./, (letter) => letter.toUpperCase()),
+ status: faker.helpers.shuffle(tasks.status.enumValues)[0] ?? "todo",
+ label: faker.helpers.shuffle(tasks.label.enumValues)[0] ?? "bug",
+ priority: faker.helpers.shuffle(tasks.priority.enumValues)[0] ?? "low",
+ archived: faker.datatype.boolean({ probability: 0.2 }),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ }
+ }
+
+
+export async function seedTasks(input: { count: number }) {
+ const count = input.count ?? 100
+
+ try {
+ const allTasks: NewTask[] = []
+
+ for (let i = 0; i < count; i++) {
+ allTasks.push(generateRandomTask())
+ }
+
+ await db.delete(tasks)
+
+ console.log("📝 Inserting tasks", allTasks.length)
+
+ await db.insert(tasks).values(allTasks).onConflictDoNothing()
+ } catch (err) {
+ console.error(err)
+ }
+}
diff --git a/db/seeds/users.ts b/db/seeds/users.ts
new file mode 100644
index 00000000..e1d26b38
--- /dev/null
+++ b/db/seeds/users.ts
@@ -0,0 +1,21 @@
+import { seedUsers } from "./userseed"
+
+async function runSeed() {
+ console.log("⏳ Running seed...")
+
+ const start = Date.now()
+
+ await seedUsers({ count: 100 })
+
+ const end = Date.now()
+
+ console.log(`✅ Seed completed in ${end - start}ms`)
+
+ process.exit(0)
+}
+
+runSeed().catch((err) => {
+ console.error("❌ Seed failed")
+ console.error(err)
+ process.exit(1)
+})
diff --git a/db/seeds/userseed.ts b/db/seeds/userseed.ts
new file mode 100644
index 00000000..92622c78
--- /dev/null
+++ b/db/seeds/userseed.ts
@@ -0,0 +1,36 @@
+import db from "@/db/db"
+import { NewUser } from "@/lib/admin-users/repository"
+import { faker } from "@faker-js/faker"
+import { users } from "../schema/users"
+
+
+function generateRandomUser(): NewUser {
+ return {
+ name: faker.person.fullName(), // 또는 faker.company.name()
+ email: faker.internet.email(), // 필수 notNull 필드
+ domain: "evcp", // domain을 evcp로 고정
+ companyId: null, // companyId는 null로(빈칸)
+ imageUrl: null, // imageUrl은 null로(빈칸)
+ createdAt: faker.date.past() // 과거 임의 날짜
+ }
+}
+
+export async function seedUsers(input: { count: number }) {
+ const count = input.count ?? 100
+
+ try {
+ const allUsers: NewUser[] = []
+
+ for (let i = 0; i < count; i++) {
+ allUsers.push(generateRandomUser())
+ }
+
+ await db.delete(users)
+
+ console.log("📝 Inserting companies", allUsers.length)
+
+ await db.insert(users).values(allUsers).onConflictDoNothing()
+ } catch (err) {
+ console.error(err)
+ }
+}
diff --git a/db/seeds/vendorDocu.ts b/db/seeds/vendorDocu.ts
new file mode 100644
index 00000000..eb96ef7b
--- /dev/null
+++ b/db/seeds/vendorDocu.ts
@@ -0,0 +1,144 @@
+/**
+ * seed.ts
+ * - 드릴즐 + faker로 documents / issueStages / revisions 테이블에 테스트 데이터 삽입 예시
+ */
+
+import db from "@/db/db"
+import { eq } from "drizzle-orm"
+// faker-js/faker 또는 @faker-js/faker
+import { faker } from "@faker-js/faker"
+import { documents, issueStages, revisions } from "../schema/vendorDocu"
+
+// 몇 개의 도큐먼트를 만들지
+const NUM_DOCUMENTS = 200
+
+// 하나의 문서당 스테이지 개수 범위
+const STAGE_MIN = 1
+const STAGE_MAX = 3
+
+// 하나의 스테이지당 리비전 개수 범위
+const REV_MIN = 1
+const REV_MAX = 2
+
+// 가상으로 계약 ID가 1~5 범위라고 가정
+const CONTRACT_ID_MIN = 96
+const CONTRACT_ID_MAX = 125
+
+async function seed() {
+ try {
+ console.log("Seeding started...")
+
+ // 1) documents 테이블에 NUM_DOCUMENTS 개 생성
+ const docsData = []
+ for (let i = 0; i < NUM_DOCUMENTS; i++) {
+ const randomContractId = faker.number.int({
+ min: CONTRACT_ID_MIN,
+ max: CONTRACT_ID_MAX,
+ })
+
+ docsData.push({
+ contractId: randomContractId,
+ docNumber: `DOC-${faker.number.int({ min: 1000, max: 9999 })}`,
+ title: faker.lorem.sentence(3), // 예: "dolor sit amet"
+ status: faker.helpers.arrayElement(["ACTIVE", "REVIEW", "APPROVED"]),
+ issuedDate: faker.helpers.maybe(() => faker.date.past({ years: 1 }), {
+ probability: 0.7,
+ })
+ // createdAt, updatedAt은 defaultNow()로 자동
+ })
+ }
+
+ // documents 테이블 INSERT
+ const insertedDocs = await db.insert(documents).values(docsData).returning()
+ console.log(`Inserted ${insertedDocs.length} documents.`)
+
+ // 2) 각 Document에 대해 스테이지(issueStages) 생성
+ for (const doc of insertedDocs) {
+ // doc.id가 undefined인지 확인
+ if (!doc.id) {
+ console.error("Document ID is undefined:", doc)
+ continue
+ }
+
+ const stageCount = faker.number.int({ min: STAGE_MIN, max: STAGE_MAX })
+ const stagesData = []
+ for (let j = 0; j < stageCount; j++) {
+ stagesData.push({
+ documentId: doc.id,
+ stageName: faker.helpers.arrayElement([
+ "Issued for Review",
+ "IFC",
+ "AFC",
+ "As-Built",
+ ]),
+ planDate: faker.helpers.maybe(() => faker.date.future({ years: 0.5 }), {
+ probability: 0.5,
+ }),
+ actualDate: faker.helpers.maybe(() => faker.date.future({ years: 0.5 }), {
+ probability: 0.5,
+ })
+ // 50% 확률로 실제일
+ })
+ }
+
+ // 스테이지 데이터가 비어 있는지 확인
+ if (stagesData.length === 0) {
+ console.warn(`No stages created for document ${doc.id}`)
+ continue
+ }
+
+ try {
+ const insertedStages = await db.insert(issueStages).values(stagesData).returning()
+ console.log(`Inserted ${insertedStages.length} stages for document ${doc.id}`)
+
+ // 3) 각 스테이지별 리비전(revisions) 생성
+ for (const stage of insertedStages) {
+ // stage.id가 undefined인지 확인
+ if (!stage.id) {
+ console.error("Stage ID is undefined:", stage)
+ continue
+ }
+
+ const revCount = faker.number.int({ min: REV_MIN, max: REV_MAX })
+ const revData = []
+ for (let k = 0; k < revCount; k++) {
+ revData.push({
+ issueStageId: stage.id,
+ documentId: doc.id, // 문서 ID도 추가
+ revision: faker.helpers.arrayElement(["A", "B", "C", "0", "1"]),
+ filePath: faker.system.filePath(),
+ approvedDate: faker.helpers.maybe(() => faker.date.recent({ days: 10 })),
+ })
+ }
+
+ // 리비전 데이터가 비어 있는지 확인
+ if (revData.length === 0) {
+ console.warn(`No revisions created for stage ${stage.id}`)
+ continue
+ }
+
+ try {
+ const insertedRevs = await db.insert(revisions).values(revData).returning()
+ console.log(`Inserted ${insertedRevs.length} revisions for stage ${stage.id}`)
+ } catch (error) {
+ console.error(`Error inserting revisions for stage ${stage.id}:`, error)
+ }
+ }
+ } catch (error) {
+ console.error(`Error inserting stages for document ${doc.id}:`, error)
+ }
+ }
+
+ console.log("Seeding completed successfully.")
+ } catch (err) {
+ console.error("Seeding error:", err)
+ process.exit(1)
+ } finally {
+ // 필요하다면 DB connection 종료 로직
+ }
+}
+
+// 스크립트 직접 실행 시
+seed()
+ .then(() => process.exit(0))
+ .catch(() => process.exit(1)) \ No newline at end of file
diff --git a/db/seeds/vendorSeed.ts b/db/seeds/vendorSeed.ts
new file mode 100644
index 00000000..bb659562
--- /dev/null
+++ b/db/seeds/vendorSeed.ts
@@ -0,0 +1,86 @@
+// db/seed/vendorSeed.ts
+import { faker } from "@faker-js/faker";
+import db from "@/db/db";
+import { vendors, vendorContacts, vendorPossibleItems } from "../schema/vendors";
+import { items } from "../schema/items"; // items 테이블 import
+import { eq } from "drizzle-orm";
+
+async function seedVendors() {
+ // 1) 기존 데이터 정리(원하면)
+ await db.delete(vendorPossibleItems);
+ await db.delete(vendorContacts);
+ await db.delete(vendors);
+
+ // 2) 먼저 items 테이블에서 모든 아이템 목록 조회
+ const existingItems = await db.select().from(items);
+ if (existingItems.length === 0) {
+ console.log("No items found in 'items' table. Please seed items first.");
+ return;
+ }
+
+ // 3) Vendors 생성
+ const vendorCount = 10;
+
+ for (let i = 0; i < vendorCount; i++) {
+ const newVendor = {
+ vendorName: faker.company.name(), // 회사명
+ vendorCode: faker.string.alpha({ length: 6 }), // 임의 코드
+ address: faker.location.streetAddress(),
+ country: faker.location.country(),
+ phone: faker.phone.number(),
+ taxId: faker.phone.imei(),
+ email: faker.internet.email(),
+ website: faker.internet.url(),
+ status: faker.helpers.arrayElement(["ACTIVE", "INACTIVE", "BLACKLISTED","IN PQ"]),
+ };
+
+ // Vendors 테이블에 Insert 후, 결과(생성된 id)를 받음
+ const [insertedVendor] = await db.insert(vendors).values(newVendor).returning();
+ const vendorId = insertedVendor.id;
+
+ // 4) Contacts 생성 (각 벤더당 1~3명)
+ const contactCount = faker.number.int({ min: 1, max: 3 });
+ for (let j = 0; j < contactCount; j++) {
+ const newContact = {
+ vendorId,
+ contactName: faker.person.fullName(),
+ contactPosition: faker.person.jobTitle(),
+ contactEmail: faker.internet.email(),
+ contactPhone: faker.phone.number(),
+ isPrimary: j === 0,
+ };
+ await db.insert(vendorContacts).values(newContact);
+ }
+
+ // 5) Possible Items 생성 (각 벤더당 2~5개)
+ // 여기서 "items" 테이블에서 랜덤 pick → vendorPossibleItems에 참조
+ const itemCount = faker.number.int({ min: 2, max: 5 });
+
+ // 매번 하나씩 pick해도 되고, arrayElements로 여러 개를 한 번에 pick해도 됨
+ for (let k = 0; k < itemCount; k++) {
+ // 1개 item 무작위 선택
+ const randomItem = faker.helpers.arrayElement(existingItems);
+
+ // vendorPossibleItems에 Insert
+ const newItem = {
+ vendorId,
+ itemCode: randomItem.itemCode ?? "FAKECODE",
+ description: faker.commerce.productDescription(),
+ };
+ await db.insert(vendorPossibleItems).values(newItem);
+ }
+ }
+
+ console.log(`✅ Seeded ${vendorCount} vendors (with contacts & possible items).`);
+}
+
+// 스크립트 실행 부분
+seedVendors()
+ .then(() => {
+ console.log("Seeding complete!");
+ process.exit(0);
+ })
+ .catch((error) => {
+ console.error("Seeding failed:", error);
+ process.exit(1);
+ }); \ No newline at end of file