import { faker } from "@faker-js/faker"; import db from "@/db/db" import { procurementRfqs, paymentTerms, incoterms, procurementRfqDetails, prItems } from "@/db/schema"; import { eq } from "drizzle-orm"; // 랜덤 데이터 생성 시 일관성을 위해 시드 설정 faker.seed(123); // 생성할 데이터 수량 설정 const NUM_PAYMENT_TERMS = 5; const NUM_INCOTERMS = 7; const NUM_RFQS = 50; const NUM_RFQ_DETAILS_PER_RFQ = 1; // 각 RFQ마다 생성할 detail 수 const NUM_PR_ITEMS_PER_RFQ = 3; // 각 RFQ마다 생성할 PR item 수 /** * 기존 데이터를 가져오는 함수들 */ async function getRandomUser() { const users = await db.query.users.findMany({ limit: 50 }); return faker.helpers.arrayElement(users); } async function getRandomProject() { const projects = await db.query.projects.findMany({ limit: 50 }); return faker.helpers.arrayElement(projects); } async function getRandomItem() { const items = await db.query.items.findMany({ limit: 50 }); return faker.helpers.arrayElement(items); } async function getRandomVendor() { const vendors = await db.query.vendors.findMany({ limit: 50 }); return faker.helpers.arrayElement(vendors); } /** * 시드 데이터 생성 함수 */ // 지불 조건 시드 데이터 생성 async function seedPaymentTerms() { console.log("🌱 시드 데이터 생성 중: 지불 조건"); // 기존 데이터 확인 const existingTerms = await db.query.paymentTerms.findMany(); if (existingTerms.length > 0) { console.log(" ✓ 지불 조건 데이터가 이미 존재합니다."); return existingTerms; } const terms = [ { code: "NET30", description: "Net 30 days" }, { code: "NET60", description: "Net 60 days" }, { code: "COD", description: "Cash on Delivery" }, { code: "ADV", description: "Advance Payment" }, { code: "L/C", description: "Letter of Credit" } ]; const randomUser = await getRandomUser(); const paymentTermsData = terms.map(term => ({ code: term.code, description: term.description, isActive: true, createdBy: randomUser.id, createdAt: new Date() })); await db.insert(paymentTerms).values(paymentTermsData); console.log(` ✓ ${terms.length}개의 지불 조건이 생성되었습니다.`); return await db.query.paymentTerms.findMany(); } // 인코텀즈 시드 데이터 생성 async function seedIncoterms() { console.log("🌱 시드 데이터 생성 중: 인코텀즈"); // 기존 데이터 확인 const existingIncoterms = await db.query.incoterms.findMany(); if (existingIncoterms.length > 0) { console.log(" ✓ 인코텀즈 데이터가 이미 존재합니다."); return existingIncoterms; } const terms = [ { code: "EXW", description: "Ex Works" }, { code: "FCA", description: "Free Carrier" }, { code: "FAS", description: "Free Alongside Ship" }, { code: "FOB", description: "Free On Board" }, { code: "CFR", description: "Cost and Freight" }, { code: "CIF", description: "Cost, Insurance and Freight" }, { code: "DDP", description: "Delivered Duty Paid" } ]; const randomUser = await getRandomUser(); const incotermsData = terms.map(term => ({ code: term.code, description: term.description, isActive: true, createdBy: randomUser.id, createdAt: new Date() })); await db.insert(incoterms).values(incotermsData); console.log(` ✓ ${terms.length}개의 인코텀즈가 생성되었습니다.`); return await db.query.incoterms.findMany(); } // RFQ 시드 데이터 생성 async function seedRfqs() { console.log("🌱 시드 데이터 생성 중: RFQ"); // 기존 데이터 확인 const existingRfqs = await db.query.procurementRfqs.findMany(); if (existingRfqs.length > 0) { console.log(` ✓ RFQ 데이터가 이미 ${existingRfqs.length}개 존재합니다.`); return existingRfqs; } const statusOptions = [ "RFQ Created", "RFQ Vendor Assignned", "RFQ Sent", "Quotation Analysis", "PO Transfer", "PO Create" ]; const rfqsData = []; for (let i = 1; i <= NUM_RFQS; i++) { const randomUser = await getRandomUser(); const randomProject = await getRandomProject(); const randomItem = await getRandomItem(); // RFQ 전송일은 과거 1~90일 사이 const rfqSendDate = faker.date.past({ days: 90 }); // 마감일은 전송일로부터 7~30일 이후 const dueDate = new Date(rfqSendDate); dueDate.setDate(dueDate.getDate() + faker.number.int({ min: 7, max: 30 })); const series = faker.helpers.arrayElement(["A", "B", "C", "D", "E"]); const rfqCode = `RFQ-${new Date().getFullYear()}-${String(i).padStart(3, '0')}`; rfqsData.push({ rfqCode, projectId: randomProject.id, series, itemId: randomItem.id, dueDate, rfqSendDate, status: faker.helpers.arrayElement(statusOptions), rfqSealedYn: faker.datatype.boolean(0.3), // 30% 확률로 true picCode: faker.person.lastName() + faker.string.numeric(2), // 발주담당 코드 remark: faker.helpers.maybe(() => faker.lorem.sentence(), { probability: 0.6 }), createdBy: randomUser.id, updatedBy: randomUser.id, createdAt: faker.date.recent({ days: 100 }), updatedAt: new Date() }); } // 청크로 나누어 대용량 데이터 삽입 const chunkSize = 10; for (let i = 0; i < rfqsData.length; i += chunkSize) { const chunk = rfqsData.slice(i, i + chunkSize); await db.insert(procurementRfqs).values(chunk); } console.log(` ✓ ${rfqsData.length}개의 RFQ가 생성되었습니다.`); return await db.query.procurementRfqs.findMany(); } // RFQ 상세 정보 시드 데이터 생성 async function seedRfqDetails(rfqs, paymentTermsList, incotermsList) { console.log("🌱 시드 데이터 생성 중: RFQ 상세 정보"); // 기존 데이터 확인 const existingDetails = await db.query.procurementRfqDetails.findMany(); if (existingDetails.length > 0) { console.log(` ✓ RFQ 상세 정보가 이미 ${existingDetails.length}개 존재합니다.`); return existingDetails; } const rfqDetailsData = []; for (const rfq of rfqs) { for (let i = 0; i < NUM_RFQ_DETAILS_PER_RFQ; i++) { const randomVendor = await getRandomVendor(); const randomUser = await getRandomUser(); const paymentTerm = faker.helpers.arrayElement(paymentTermsList); const incoterm = faker.helpers.arrayElement(incotermsList); // 배송일은 RFQ 마감일로부터 14~60일 이후 const deliveryDate = new Date(rfq.dueDate); deliveryDate.setDate(deliveryDate.getDate() + faker.number.int({ min: 14, max: 60 })); const currencies = ["USD", "EUR", "JPY", "KRW", "CNY"]; rfqDetailsData.push({ procurementRfqsId: rfq.id, vendorsId: randomVendor.id, currency: faker.helpers.arrayElement(currencies), paymentTermsCode: paymentTerm.code, incotermsCode: incoterm.code, incotermsDetail: faker.helpers.maybe(() => faker.lorem.sentence(), { probability: 0.7 }), deliveryDate, taxCode: faker.helpers.arrayElement(["VV", "V1", "V2", "V0"]), placeOfShipping: faker.location.city() + ", " + faker.location.country(), placeOfDestination: faker.location.city() + ", " + faker.location.country(), remark: faker.helpers.maybe(() => faker.lorem.paragraph(), { probability: 0.5 }), cancelReason: faker.helpers.maybe(() => faker.lorem.sentence(), { probability: 0.1 }), updatedBy: randomUser.id, updatedAt: new Date(), materialPriceRelatedYn: faker.datatype.boolean(0.4) // 40% 확률로 true }); } } // 청크로 나누어 대용량 데이터 삽입 const chunkSize = 20; for (let i = 0; i < rfqDetailsData.length; i += chunkSize) { const chunk = rfqDetailsData.slice(i, i + chunkSize); await db.insert(procurementRfqDetails).values(chunk); } console.log(` ✓ ${rfqDetailsData.length}개의 RFQ 상세 정보가 생성되었습니다.`); return await db.query.procurementRfqDetails.findMany(); } // PR 아이템 시드 데이터 생성 async function seedPrItems(rfqs) { console.log("🌱 시드 데이터 생성 중: PR 아이템"); // 기존 데이터 확인 const existingItems = await db.query.prItems.findMany(); if (existingItems.length > 0) { console.log(` ✓ PR 아이템이 이미 ${existingItems.length}개 존재합니다.`); return existingItems; } const prItemsData = []; for (const rfq of rfqs) { // 각 RFQ마다 여러 개의 PR 아이템 생성 for (let i = 0; i < NUM_PR_ITEMS_PER_RFQ; i++) { const randomItem = await getRandomItem(); // 하나의 RFQ에 하나의 major item만 존재하도록 설정 const majorYn = i === 0; // 배송일은 RFQ 전송일로부터 30~120일 이후 const deliveryDate = new Date(rfq.rfqSendDate); deliveryDate.setDate(deliveryDate.getDate() + faker.number.int({ min: 30, max: 120 })); const materialCode = faker.string.alphanumeric(8).toUpperCase(); const rfqItem = `${String(i + 1).padStart(2, '0')}`; const prItem = `${String(i + 1).padStart(2, '0')}`; const prNo = `PR-${new Date().getFullYear()}-${faker.string.numeric(4)}`; prItemsData.push({ procurementRfqsId: rfq.id, rfqItem, prItem, prNo, itemId: randomItem.id, materialCode, materialCategory: faker.helpers.arrayElement(["RAW", "SEMI", "FIN", "SERV", "TOOL"]), acc: faker.string.alphanumeric(6).toUpperCase(), materialDescription: faker.commerce.productName(), size: faker.helpers.maybe(() => `${faker.number.int({ min: 10, max: 200 })}x${faker.number.int({ min: 10, max: 200 })}`, { probability: 0.7 }), deliveryDate, quantity: faker.number.float({ min: 1, max: 1000, precision: 0.01 }), uom: faker.helpers.arrayElement(["EA", "KG", "M", "L", "SET"]), grossWeight: faker.number.float({ min: 0.1, max: 500, precision: 0.01 }), gwUom: faker.helpers.arrayElement(["KG", "TON"]), specNo: faker.helpers.maybe(() => `SPEC-${faker.string.alphanumeric(6).toUpperCase()}`, { probability: 0.6 }), specUrl: faker.helpers.maybe(() => faker.internet.url(), { probability: 0.4 }), trackingNo: faker.helpers.maybe(() => `TRK-${faker.string.alphanumeric(8).toUpperCase()}`, { probability: 0.5 }), majorYn, projectDef: faker.helpers.maybe(() => faker.string.alphanumeric(5).toUpperCase(), { probability: 0.8 }), projectSc: faker.helpers.maybe(() => faker.string.alphanumeric(5).toUpperCase(), { probability: 0.7 }), projectKl: faker.helpers.maybe(() => faker.string.alphanumeric(5).toUpperCase(), { probability: 0.7 }), projectLc: faker.helpers.maybe(() => faker.string.alphanumeric(5).toUpperCase(), { probability: 0.7 }), projectDl: faker.helpers.maybe(() => faker.string.alphanumeric(5).toUpperCase(), { probability: 0.7 }), remark: faker.helpers.maybe(() => faker.lorem.sentence(), { probability: 0.4 }) }); } } // 청크로 나누어 대용량 데이터 삽입 const chunkSize = 30; for (let i = 0; i < prItemsData.length; i += chunkSize) { const chunk = prItemsData.slice(i, i + chunkSize); await db.insert(prItems).values(chunk); } console.log(` ✓ ${prItemsData.length}개의 PR 아이템이 생성되었습니다.`); return await db.query.prItems.findMany(); } /** * 시드 실행 함수 */ export async function seedProcurement() { console.log("🔍 procurement 데이터 시딩 시작..."); try { // 순서대로 데이터 시딩 const paymentTermsList = await seedPaymentTerms(); const incotermsList = await seedIncoterms(); const rfqsList = await seedRfqs(); await seedRfqDetails(rfqsList, paymentTermsList, incotermsList); await seedPrItems(rfqsList); console.log("✅ procurement 데이터 시딩 완료!"); } catch (error) { console.error("❌ 데이터 시딩 중 오류 발생:", error); throw error; } } // 스크립트가 직접 실행될 때만 시드 함수 호출 if (require.main === module) { seedProcurement() .then(() => { console.log("시드 스크립트 실행 완료. 프로세스를 종료합니다."); process.exit(0); }) .catch((error) => { console.error("시드 스크립트 실행 중 오류:", error); process.exit(1); }); }