summaryrefslogtreecommitdiff
path: root/db/seeds_2
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-03-26 00:37:41 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-03-26 00:37:41 +0000
commite0dfb55c5457aec489fc084c4567e791b4c65eb1 (patch)
tree68543a65d88f5afb3a0202925804103daa91bc6f /db/seeds_2
3/25 까지의 대표님 작업사항
Diffstat (limited to 'db/seeds_2')
-rw-r--r--db/seeds_2/companySeed.ts38
-rw-r--r--db/seeds_2/itmeSeed.ts108
-rw-r--r--db/seeds_2/rfqSeed.ts148
-rw-r--r--db/seeds_2/seed.ts56
-rw-r--r--db/seeds_2/taskSeed.ts46
-rw-r--r--db/seeds_2/userSeed.ts39
-rw-r--r--db/seeds_2/vendorSeed.ts105
7 files changed, 540 insertions, 0 deletions
diff --git a/db/seeds_2/companySeed.ts b/db/seeds_2/companySeed.ts
new file mode 100644
index 00000000..4293b02b
--- /dev/null
+++ b/db/seeds_2/companySeed.ts
@@ -0,0 +1,38 @@
+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 }) {
+ try {
+ const allCompanies: NewCompany[] = []
+
+ for (let i = 0; i < input.count; i++) {
+ allCompanies.push(generateRandomCompany())
+ }
+
+ console.log("📝 Inserting companies", allCompanies.length)
+
+ // onConflictDoNothing을 사용하여 중복되는 데이터는 건너뛰고 새로운 데이터만 추가
+ const result = await db.insert(companies)
+ .values(allCompanies)
+ .onConflictDoNothing()
+ .returning()
+
+ console.log(`✅ Successfully added ${result.length} new companies`)
+ return result
+ } catch (err) {
+ console.error("Failed to seed companies:", err)
+ throw err
+ }
+}
diff --git a/db/seeds_2/itmeSeed.ts b/db/seeds_2/itmeSeed.ts
new file mode 100644
index 00000000..6d725c22
--- /dev/null
+++ b/db/seeds_2/itmeSeed.ts
@@ -0,0 +1,108 @@
+// db/seed/itemSeed.ts
+import { faker } from "@faker-js/faker"
+import db from "@/db/db"
+import { items } from "../schema/items"
+import { desc } from "drizzle-orm"
+
+function generateItemName(category: string): string {
+ const namePattern = Math.random()
+ if (namePattern < 0.4) {
+ // 일반적인 제품명
+ return `${category} ${faker.commerce.productName()}`
+ } else if (namePattern < 0.7) {
+ // 기술 사양이 포함된 이름
+ const spec = faker.number.int({ min: 100, max: 9999 })
+ return `${category} ${faker.commerce.productName()} ${spec}`
+ } else {
+ // 브랜드가 포함된 이름
+ const brand = faker.company.name().split(" ")[0]
+ return `${brand} ${category} ${faker.commerce.productName()}`
+ }
+}
+
+async function generateItem(industry: string, category: string, index: number) {
+ const itemCode = `${industry.substring(0, 3).toUpperCase()}-${category.substring(0, 3).toUpperCase()}-${String(index + 1).padStart(4, "0")}`
+
+ return {
+ itemCode,
+ itemName: generateItemName(category),
+ description: `${industry} - ${category}: ${faker.commerce.productDescription()}`,
+ }
+}
+
+export async function seedItems(input: { itemsPerCategory: number }) {
+ try {
+ // 다양한 산업군별 아이템 카테고리 정의
+ const categories = {
+ Electronics: ["Semiconductors", "PCB Components", "Sensors", "Displays", "Batteries"],
+ Automotive: ["Engine Parts", "Transmission", "Suspension", "Electrical", "Interior"],
+ Chemical: ["Industrial Chemicals", "Polymers", "Coatings", "Adhesives", "Solvents"],
+ Construction: ["Steel", "Concrete", "Lumber", "Insulation", "Hardware"],
+ Manufacturing: ["Tools", "Machinery", "Spare Parts", "Raw Materials", "Equipment"]
+ } as const;
+
+ // 현재 가장 큰 itemCode 찾기
+ const lastItems = await db.select()
+ .from(items)
+ .orderBy(desc(items.itemCode))
+ .limit(Object.keys(categories).length * 5); // 각 산업별 서브카테고리 수
+
+ // 각 카테고리별 마지막 인덱스 추적
+ const lastIndices: Record<keyof typeof categories, Record<string, number>> = {
+ Electronics: {},
+ Automotive: {},
+ Chemical: {},
+ Construction: {},
+ Manufacturing: {},
+ };
+
+ // 기존 아이템의 itemCode에서 카테고리와 인덱스 추출
+ for (const item of lastItems) {
+ if (!item.itemCode) continue;
+
+ const [industryCode, categoryCode, indexStr] = item.itemCode.split("-");
+ if (!industryCode || !categoryCode || !indexStr) continue;
+
+ // 코드로부터 원래 카테고리 찾기
+ const industry = Object.keys(categories).find(ind => ind.substring(0, 3).toUpperCase() === industryCode) as keyof typeof categories;
+ if (!industry) continue;
+
+ const category = categories[industry].find(cat => cat.substring(0, 3).toUpperCase() === categoryCode);
+ if (!category) continue;
+
+ if (!lastIndices[industry]) {
+ lastIndices[industry] = {};
+ }
+
+ const itemIndex = parseInt(indexStr);
+ if (!lastIndices[industry][category] || itemIndex > lastIndices[industry][category]) {
+ lastIndices[industry][category] = itemIndex;
+ }
+ }
+
+ const allItems = [];
+
+ for (const [industry, subcategories] of Object.entries(categories)) {
+ for (const category of subcategories) {
+ const startIndex = (lastIndices[industry as keyof typeof categories]?.[category] ?? 0);
+
+ for (let i = 0; i < input.itemsPerCategory; i++) {
+ allItems.push(await generateItem(industry, category, startIndex + i));
+ }
+ }
+ }
+
+ console.log("📝 Inserting items", allItems.length);
+
+ const result = await db.insert(items)
+ .values(allItems)
+ .onConflictDoNothing()
+ .returning();
+
+ console.log(`✅ Successfully added ${result.length} new items across ${Object.keys(categories).length} industries`);
+ return result;
+ } catch (err) {
+ console.error("Failed to seed items:", err);
+ throw err;
+ }
+} \ No newline at end of file
diff --git a/db/seeds_2/rfqSeed.ts b/db/seeds_2/rfqSeed.ts
new file mode 100644
index 00000000..f2cde1b2
--- /dev/null
+++ b/db/seeds_2/rfqSeed.ts
@@ -0,0 +1,148 @@
+// db/seed/rfqSeed.ts
+import { faker } from "@faker-js/faker";
+import db from "@/db/db"
+import { eq, inArray, desc } 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";
+import { users } from "../schema/users";
+import { items } from "../schema/items";
+
+async function generateRfq(index: number, allUsers: any[], allVendors: any[], allItems: any[]) {
+ const year = faker.number.int({ min: 2022, max: 2024 });
+ const sequence = String(index + 1).padStart(3, "0");
+ const rfqCode = `RFQ-${year}-${sequence}`;
+
+ // 상태별 비율 설정
+ const statusDistribution = {
+ DRAFT: 0.3, // 30%
+ PUBLISHED: 0.3, // 30%
+ EVALUATION: 0.2, // 20%
+ AWARDED: 0.2 // 20%
+ };
+
+ // 랜덤 상태 결정
+ const rand = Math.random();
+ let status: "DRAFT" | "PUBLISHED" | "EVALUATION" | "AWARDED";
+ if (rand < statusDistribution.DRAFT) status = "DRAFT";
+ else if (rand < statusDistribution.DRAFT + statusDistribution.PUBLISHED) status = "PUBLISHED";
+ else if (rand < statusDistribution.DRAFT + statusDistribution.PUBLISHED + statusDistribution.EVALUATION) status = "EVALUATION";
+ else status = "AWARDED";
+
+ // RFQ 생성
+ const [rfq] = await db.insert(rfqs).values({
+ rfqCode,
+ description: faker.commerce.productDescription(),
+ projectCode: `PRJ-${faker.string.alphanumeric(6).toUpperCase()}`,
+ projectName: faker.company.catchPhrase(),
+ dueDate: faker.date.future(),
+ status,
+ createdBy: faker.helpers.arrayElement(allUsers).id,
+ }).returning();
+
+ // 3-10개의 items 추가
+ const itemCount = faker.number.int({ min: 3, max: 10 });
+ const selectedItems = faker.helpers.arrayElements(allItems, itemCount);
+ for (const item of selectedItems) {
+ await db.insert(rfqItems).values({
+ rfqId: rfq.id,
+ itemCode: item.itemCode,
+ description: faker.commerce.productDescription(),
+ quantity: faker.number.float({ min: 1, max: 1000, fractionDigits: 2 }),
+ uom: faker.helpers.arrayElement(["EA", "KG", "M", "L", "SET"]),
+ });
+ }
+
+ // 2-5개의 vendors 초대
+ const vendorCount = faker.number.int({ min: 2, max: 5 });
+ const selectedVendors = faker.helpers.arrayElements(allVendors, vendorCount);
+ for (const vendor of selectedVendors) {
+ await db.insert(rfqVendors).values({
+ rfqId: rfq.id,
+ vendorId: vendor.id,
+ status: faker.helpers.arrayElement(["INVITED", "ACCEPTED", "REJECTED", "QUOTED"]),
+ });
+
+ // 50% 확률로 quote 생성
+ if (Math.random() > 0.5) {
+ await db.insert(vendorQuotes).values({
+ rfqId: rfq.id,
+ vendorId: vendor.id,
+ totalAmount: faker.number.float({ min: 1000, max: 1000000, fractionDigits: 2 }),
+ currency: faker.helpers.arrayElement(["USD", "EUR", "KRW"]),
+ leadTime: `${faker.number.int({ min: 1, max: 52 })} weeks`,
+ notes: faker.lorem.paragraph(),
+ });
+ }
+
+ // 30% 확률로 attachment 추가
+ if (Math.random() > 0.7) {
+ await db.insert(rfqAttachments).values({
+ rfqId: rfq.id,
+ vendorId: vendor.id,
+ fileName: `${faker.system.fileName()}.pdf`,
+ filePath: `/uploads/rfq/${rfq.id}/${faker.system.fileName()}.pdf`,
+ });
+ }
+
+ // 40% 확률로 comment 추가
+ if (Math.random() > 0.6) {
+ await db.insert(rfqComments).values({
+ rfqId: rfq.id,
+ vendorId: vendor.id,
+ commentText: faker.lorem.paragraph(),
+ commentedBy: faker.helpers.arrayElement(allUsers).id,
+ });
+ }
+
+ // EVALUATION 또는 AWARDED 상태인 경우 평가 데이터 추가
+ if (status === "EVALUATION" || status === "AWARDED") {
+ await db.insert(rfqEvaluations).values({
+ rfqId: rfq.id,
+ vendorId: vendor.id,
+ evalType: faker.helpers.arrayElement(["TBE", "CBE"]),
+ result: faker.helpers.arrayElement(["PASS", "FAIL", "ACCEPTABLE"]),
+ notes: faker.lorem.paragraph(),
+ });
+ }
+ }
+
+ return rfq;
+}
+
+export async function seedRfqData(input: { count: number }) {
+ try {
+ const allUsers = await db.select().from(users);
+ const allVendors = await db.select().from(vendors);
+ const allItems = await db.select().from(items);
+
+ // 현재 가장 큰 RFQ 코드 찾기
+ const [lastRfq] = await db.select()
+ .from(rfqs)
+ .orderBy(desc(rfqs.rfqCode))
+ .limit(1);
+
+ // RFQ-YYYY-ddd 형식에서 마지막 번호 추출
+ const startIndex = lastRfq?.rfqCode
+ ? parseInt(lastRfq.rfqCode.split('-')[2])
+ : 0;
+
+ console.log("📝 Generating RFQ data...", { lastRfqCode: lastRfq?.rfqCode, startIndex });
+
+ const generatedRfqs = [];
+ for (let i = 0; i < input.count; i++) {
+ const rfq = await generateRfq(startIndex + i, allUsers, allVendors, allItems);
+ generatedRfqs.push(rfq);
+ }
+
+ console.log(`✅ Successfully generated ${generatedRfqs.length} new RFQs with related data`);
+ return generatedRfqs;
+ } catch (err) {
+ console.error("Failed to seed RFQ data:", err);
+ throw err;
+ }
+} \ No newline at end of file
diff --git a/db/seeds_2/seed.ts b/db/seeds_2/seed.ts
new file mode 100644
index 00000000..2a21fc6e
--- /dev/null
+++ b/db/seeds_2/seed.ts
@@ -0,0 +1,56 @@
+import { seedCompanies } from "./companySeed"
+import { seedItems } from "./itmeSeed"
+import { seedRfqData } from "./rfqSeed"
+import { seedTasks } from "./taskSeed"
+import { seedUsers } from "./userSeed"
+import { seedVendors } from "./vendorSeed"
+
+// 시드 데이터 수 중앙 관리
+const SEED_COUNTS = {
+ companies: 100,
+ users: 100,
+ items: {
+ itemsPerCategory: 100
+ },
+ vendors: 100,
+ tasks: 100,
+ rfqs: 100
+} as const;
+
+async function runAllSeeds() {
+ console.log("⏳ 전체 시드 실행 시작...")
+ const start = Date.now()
+
+ try {
+ // 1. 먼저 독립적인 기본 데이터 생성
+ await seedCompanies({ count: SEED_COUNTS.companies })
+ console.log("✅ Companies 시드 완료")
+
+ await seedUsers({ count: SEED_COUNTS.users })
+ console.log("✅ Users 시드 완료")
+
+ await seedItems({ itemsPerCategory: SEED_COUNTS.items.itemsPerCategory })
+ console.log("✅ Items 시드 완료")
+
+ // 2. FK 참조가 있는 데이터 생성
+ await seedVendors({ count: SEED_COUNTS.vendors })
+ console.log("✅ Vendors 시드 완료")
+
+ await seedTasks({ count: SEED_COUNTS.tasks })
+ console.log("✅ Tasks 시드 완료")
+
+ // 3. 가장 많은 FK 참조가 있는 RFQ 마지막에 생성
+ await seedRfqData({ count: SEED_COUNTS.rfqs })
+ console.log("✅ RFQ 시드 완료")
+
+ const end = Date.now()
+ console.log(`✅ 모든 시드가 ${end - start}ms 만에 완료되었습니다.`)
+ process.exit(0)
+ } catch (err) {
+ console.error("❌ 시드 실행 중 오류 발생:")
+ console.error(err)
+ process.exit(1)
+ }
+}
+
+runAllSeeds() \ No newline at end of file
diff --git a/db/seeds_2/taskSeed.ts b/db/seeds_2/taskSeed.ts
new file mode 100644
index 00000000..24fcc0e8
--- /dev/null
+++ b/db/seeds_2/taskSeed.ts
@@ -0,0 +1,46 @@
+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 }) {
+ try {
+ const allTasks: NewTask[] = []
+
+ for (let i = 0; i < input.count; i++) {
+ allTasks.push(generateRandomTask())
+ }
+
+ console.log("📝 Inserting tasks", allTasks.length)
+
+ const result = await db.insert(tasks)
+ .values(allTasks)
+ .onConflictDoNothing()
+ .returning()
+
+ console.log(`✅ Successfully added ${result.length} new tasks`)
+ return result
+ } catch (err) {
+ console.error("Failed to seed tasks:", err)
+ throw err
+ }
+}
diff --git a/db/seeds_2/userSeed.ts b/db/seeds_2/userSeed.ts
new file mode 100644
index 00000000..c0258d5e
--- /dev/null
+++ b/db/seeds_2/userSeed.ts
@@ -0,0 +1,39 @@
+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 }) {
+ try {
+ const allUsers: NewUser[] = []
+
+ for (let i = 0; i < input.count; i++) {
+ allUsers.push(generateRandomUser())
+ }
+
+ console.log("📝 Inserting users", allUsers.length)
+
+ // onConflictDoNothing을 사용하여 중복되는 데이터는 건너뛰고 새로운 데이터만 추가
+ const result = await db.insert(users)
+ .values(allUsers)
+ .onConflictDoNothing()
+ .returning()
+
+ console.log(`✅ Successfully added ${result.length} new users`)
+ return result
+ } catch (err) {
+ console.error("Failed to seed users:", err)
+ throw err
+ }
+}
diff --git a/db/seeds_2/vendorSeed.ts b/db/seeds_2/vendorSeed.ts
new file mode 100644
index 00000000..98e0a98b
--- /dev/null
+++ b/db/seeds_2/vendorSeed.ts
@@ -0,0 +1,105 @@
+// db/seed/vendorSeed.ts
+import { faker } from "@faker-js/faker";
+import db from "@/db/db";
+import { vendors } from "../schema/vendors";
+import { desc } from "drizzle-orm";
+
+export async function generateVendor(index: number) {
+ const vendorTypes = ["MANUFACTURER", "DISTRIBUTOR", "SERVICE_PROVIDER"];
+ const countries = ["KR", "US", "CN", "JP", "DE", "FR", "GB", "SG", "VN", "TH"];
+ const businessTypes = ["Corporation", "LLC", "Partnership", "Sole Proprietorship"];
+
+ const country = faker.helpers.arrayElement(countries);
+ const businessType = faker.helpers.arrayElement(businessTypes);
+ const vendorType = faker.helpers.arrayElement(vendorTypes);
+
+ // 회사 이름 생성 패턴 다양화
+ let vendorName;
+ const namePattern = Math.random();
+ if (namePattern < 0.3) {
+ // 일반적인 회사명
+ vendorName = faker.company.name();
+ } else if (namePattern < 0.6) {
+ // 산업 특화된 이름
+ const industry = faker.helpers.arrayElement([
+ "Tech", "Manufacturing", "Electronics", "Chemical",
+ "Automotive", "Energy", "Construction"
+ ]);
+ vendorName = `${faker.company.name()} ${industry}`;
+ } else {
+ // 지역성이 있는 이름
+ const region = faker.location.state();
+ vendorName = `${region} ${faker.company.name()}`;
+ }
+
+ return {
+ vendorCode: `VEN${String(index + 1).padStart(5, "0")}`,
+ vendorName,
+ businessNumber: faker.helpers.replaceSymbols("##-###-#####"),
+ representativeName: faker.person.fullName(),
+ businessType,
+ vendorType,
+ status: faker.helpers.arrayElement(["ACTIVE", "INACTIVE", "BLACKLISTED"]),
+ country,
+ address: faker.location.streetAddress(),
+ city: faker.location.city(),
+ state: faker.location.state(),
+ zipCode: faker.location.zipCode(),
+ contact1Name: faker.person.fullName(),
+ contact1Email: faker.internet.email(),
+ contact1Phone: faker.phone.number(),
+ contact2Name: Math.random() > 0.5 ? faker.person.fullName() : null,
+ contact2Email: Math.random() > 0.5 ? faker.internet.email() : null,
+ contact2Phone: Math.random() > 0.5 ? faker.phone.number() : null,
+ description: faker.company.catchPhrase(),
+ website: Math.random() > 0.3 ? faker.internet.url() : null,
+ foundedYear: faker.number.int({ min: 1950, max: 2023 }),
+ employeeCount: faker.number.int({ min: 10, max: 10000 }),
+ annualRevenue: faker.number.float({
+ min: 1000000,
+ max: 1000000000,
+ fractionDigits: 2
+ }),
+ certifications: Math.random() > 0.5 ?
+ faker.helpers.arrayElements(
+ ["ISO 9001", "ISO 14001", "OHSAS 18001", "ISO/TS 16949", "AS9100"],
+ faker.number.int({ min: 1, max: 3 })
+ ) :
+ null,
+ };
+}
+
+export async function seedVendors(input: { count: number }) {
+ try {
+ const allVendors = [];
+
+ // 현재 가장 큰 vendorCode 찾기
+ const [lastVendor] = await db.select()
+ .from(vendors)
+ .orderBy(desc(vendors.vendorCode))
+ .limit(1);
+
+ const startIndex = lastVendor?.vendorCode
+ ? parseInt(lastVendor.vendorCode.replace('VEN', ''))
+ : 0;
+
+ console.log("📝 Inserting vendors", { lastVendorCode: lastVendor?.vendorCode, startIndex });
+
+ for (let i = 0; i < input.count; i++) {
+ allVendors.push(await generateVendor(startIndex + i));
+ }
+
+ console.log("📝 Inserting vendors", allVendors.length);
+
+ const result = await db.insert(vendors)
+ .values(allVendors)
+ .onConflictDoNothing()
+ .returning();
+
+ console.log(`✅ Successfully added ${result.length} new vendors`);
+ return result;
+ } catch (err) {
+ console.error("Failed to seed vendors:", err);
+ throw err;
+ }
+} \ No newline at end of file