diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-03-26 00:37:41 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-03-26 00:37:41 +0000 |
| commit | e0dfb55c5457aec489fc084c4567e791b4c65eb1 (patch) | |
| tree | 68543a65d88f5afb3a0202925804103daa91bc6f /db/seeds | |
3/25 까지의 대표님 작업사항
Diffstat (limited to 'db/seeds')
| -rw-r--r-- | db/seeds/companies.ts | 21 | ||||
| -rw-r--r-- | db/seeds/companyseed.ts | 35 | ||||
| -rw-r--r-- | db/seeds/contract.ts | 197 | ||||
| -rw-r--r-- | db/seeds/itmeSeed.ts | 40 | ||||
| -rw-r--r-- | db/seeds/projects.ts | 43 | ||||
| -rw-r--r-- | db/seeds/rfqSeed.ts | 147 | ||||
| -rw-r--r-- | db/seeds/seedDcoments.ts | 57 | ||||
| -rw-r--r-- | db/seeds/seedIssueStage.ts | 76 | ||||
| -rw-r--r-- | db/seeds/seedRevision.ts | 173 | ||||
| -rw-r--r-- | db/seeds/seedRevisionSHI.ts | 162 | ||||
| -rw-r--r-- | db/seeds/tasks.ts | 21 | ||||
| -rw-r--r-- | db/seeds/taskseed.ts | 43 | ||||
| -rw-r--r-- | db/seeds/users.ts | 21 | ||||
| -rw-r--r-- | db/seeds/userseed.ts | 36 | ||||
| -rw-r--r-- | db/seeds/vendorDocu.ts | 144 | ||||
| -rw-r--r-- | db/seeds/vendorSeed.ts | 86 |
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 |
