summaryrefslogtreecommitdiff
path: root/db/seeds
diff options
context:
space:
mode:
Diffstat (limited to 'db/seeds')
-rw-r--r--db/seeds/contract.ts2
-rw-r--r--db/seeds/create-contract-cli.ts260
-rw-r--r--db/seeds/rfqSeed.ts6
-rw-r--r--db/seeds/vendorSeed.ts4
4 files changed, 266 insertions, 6 deletions
diff --git a/db/seeds/contract.ts b/db/seeds/contract.ts
index 1c266c72..cd2edca4 100644
--- a/db/seeds/contract.ts
+++ b/db/seeds/contract.ts
@@ -57,7 +57,7 @@ async function main() {
const NUM_CONTRACTS = 30
for (let i = 0; i < NUM_CONTRACTS; i++) {
- // 무작위 벤더, 프로젝트 선택
+ // 무작위 협력업체, 프로젝트 선택
const randomVendor = faker.helpers.arrayElement(allVendors)
const randomProject = faker.helpers.arrayElement(allProjects)
diff --git a/db/seeds/create-contract-cli.ts b/db/seeds/create-contract-cli.ts
new file mode 100644
index 00000000..3b0a8963
--- /dev/null
+++ b/db/seeds/create-contract-cli.ts
@@ -0,0 +1,260 @@
+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"
+
+// 명령행 인자 파싱
+const args = process.argv.slice(2);
+let vendorId: number | undefined,
+ projectId: number | undefined,
+ itemIds: number[] | undefined,
+ numContracts = 1,
+ shouldDeleteExisting = false;
+
+// 명령행 인자 파서
+function parseArgs() {
+ for (const arg of args) {
+ if (arg.startsWith('--vendor=')) {
+ vendorId = parseInt(arg.split('=')[1], 10);
+ } else if (arg.startsWith('--project=')) {
+ projectId = parseInt(arg.split('=')[1], 10);
+ } else if (arg.startsWith('--items=')) {
+ itemIds = arg.split('=')[1].split(',').map(id => parseInt(id.trim(), 10));
+ } else if (arg.startsWith('--count=')) {
+ numContracts = parseInt(arg.split('=')[1], 10);
+ } else if (arg === '--delete-existing') {
+ shouldDeleteExisting = true;
+ } else if (arg === '--help' || arg === '-h') {
+ showHelp();
+ process.exit(0);
+ }
+ }
+
+ if (!vendorId || !projectId || !itemIds || itemIds.length === 0) {
+ console.log("필수 인자가 누락되었습니다: vendorId, projectId, 또는 itemIds");
+ showHelp();
+ process.exit(1);
+ }
+}
+
+function showHelp() {
+ console.log(`
+사용법: npx tsx create-contract-cli.ts [옵션]
+
+필수 옵션:
+ --vendor=<vendorId> 협력업체 ID
+ --project=<projectId> 프로젝트 ID
+ --items=<itemId1,itemId2> 품목 ID (쉼표로 구분)
+
+선택 옵션:
+ --count=<num> 생성할 계약 수량 (기본값: 1)
+ --delete-existing 기존 계약 데이터 모두 삭제
+ --help, -h 도움말 표시
+
+예시:
+ npx tsx create-contract-cli.ts --vendor=1 --project=2 --items=3,4,5 --count=3
+ `);
+}
+
+async function main() {
+ console.log("계약 시딩 시작...");
+ parseArgs();
+
+ console.log(`설정: vendorId=${vendorId}, projectId=${projectId}, itemIds=[${itemIds!.join(', ')}], 계약 수량=${numContracts}`);
+
+ // DB에서 선택된 vendor, project, items 확인
+ const vendor = await db.select().from(vendors).where(eq(vendors.id, vendorId!)).limit(1);
+ if (vendor.length === 0) {
+ console.log(`ID가 ${vendorId}인 협력업체를 찾을 수 없습니다.`);
+ process.exit(1);
+ }
+
+ const project = await db.select().from(projects).where(eq(projects.id, projectId!)).limit(1);
+ if (project.length === 0) {
+ console.log(`ID가 ${projectId}인 프로젝트를 찾을 수 없습니다.`);
+ process.exit(1);
+ }
+
+ // 여러 아이템 ID 확인
+ const selectedItems = await db.select().from(items).where(
+ itemIds!.map(id => eq(items.id, id)).reduce((a, b) => db.or(a, b))
+ );
+
+ if (selectedItems.length !== itemIds!.length) {
+ const foundIds = selectedItems.map(item => item.id);
+ const missingIds = itemIds!.filter(id => !foundIds.includes(id));
+ console.log(`일부 아이템 ID를 찾을 수 없습니다: ${missingIds.join(', ')}`);
+ process.exit(1);
+ }
+
+ console.log(`협력업체: ${vendor[0].name}`);
+ console.log(`프로젝트: ${project[0].name}`);
+ console.log(`선택된 품목: ${selectedItems.map(i => i.name || i.itemCode).join(', ')}`);
+
+ // 기존 데이터 삭제 (요청된 경우)
+ if (shouldDeleteExisting) {
+ await db.delete(contractItems);
+ await db.delete(contracts);
+ console.log("기존 계약 데이터가 모두 삭제되었습니다.");
+ }
+
+ // -- 조선업 맥락에 맞는 예시 문구들 --
+ 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",
+ "거제 옥포조선소 해양공장",
+ "목포신항 부두",
+ ]
+
+ // 계약 생성 루프
+ for (let i = 0; i < numContracts; i++) {
+ // 예: C-1234
+ const contractNo = `C-${faker.number.int({ min: 1000, max: 9999 })}`
+
+ // 예: "조선 프로젝트: 벌크선 선체 건조 - (프로젝트명)"
+ const randomShipyardName = faker.helpers.arrayElement(shipyardProjectNamesKR)
+ const contractName = `조선 프로젝트: ${randomShipyardName} - ${project[0].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
+
+ // DB에 계약 insert
+ const [insertedContract] = await db
+ .insert(contracts)
+ .values({
+ projectId: projectId!,
+ vendorId: vendorId!,
+ 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(`계약 생성 완료 #${contractId}: ${contractNo}`)
+
+ // 선택한 아이템들에 대해 contract_items 생성
+ for (const itemId of itemIds!) {
+ const item = selectedItems.find(i => i.id === itemId);
+
+ const quantity = faker.number.int({ min: 1, max: 100 })
+ 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 = item?.name || faker.commerce.productName()
+ const remark = faker.helpers.maybe(() => faker.lorem.sentence(), { probability: 0.3 })
+
+ const [insertedContractItem] = await db
+ .insert(contractItems)
+ .values({
+ contractId,
+ itemId,
+ description,
+ quantity,
+ unitPrice,
+ taxRate,
+ taxAmount,
+ totalLineAmount,
+ remark: remark ?? null,
+ })
+ .returning({ id: contractItems.id })
+
+ console.log(
+ ` -> 계약 품목 생성 완료 #${insertedContractItem.id} (itemId=${itemId})`
+ )
+ }
+ }
+
+ console.log("시딩 작업이 완료되었습니다.")
+ 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
index 2d37393d..a0cebad2 100644
--- a/db/seeds/rfqSeed.ts
+++ b/db/seeds/rfqSeed.ts
@@ -20,7 +20,7 @@ export async function seedRfqData() {
await db.delete(rfqItems);
await db.delete(rfqs);
- // (벤더 목록이 이미 존재한다고 가정)
+ // (협력업체 목록이 이미 존재한다고 가정)
// 혹은 필요한 경우 vendors도 시드할 수 있음
const existingVendors = await db.select().from(vendors);
if (existingVendors.length === 0) {
@@ -68,7 +68,7 @@ export async function seedRfqData() {
}
// 3-2) RFQ Vendors
- // - 랜덤으로 2~3개 벤더를 pick
+ // - 랜덤으로 2~3개 협력업체를 pick
const pickedVendors = faker.helpers.arrayElements(existingVendors, faker.number.int({ min: 2, max: 3 }));
for (const ven of pickedVendors) {
const newRfqVendor = {
@@ -78,7 +78,7 @@ export async function seedRfqData() {
};
const [insertedRfqVendor] = await db.insert(rfqVendors).values(newRfqVendor).returning();
- // 3-3) 임의로 벤더 Quotes 생성 (50% 확률)
+ // 3-3) 임의로 협력업체 Quotes 생성 (50% 확률)
if (faker.datatype.boolean()) {
const newQuote = {
rfqId,
diff --git a/db/seeds/vendorSeed.ts b/db/seeds/vendorSeed.ts
index bb659562..4fca98d8 100644
--- a/db/seeds/vendorSeed.ts
+++ b/db/seeds/vendorSeed.ts
@@ -38,7 +38,7 @@ async function seedVendors() {
const [insertedVendor] = await db.insert(vendors).values(newVendor).returning();
const vendorId = insertedVendor.id;
- // 4) Contacts 생성 (각 벤더당 1~3명)
+ // 4) Contacts 생성 (각 협력업체당 1~3명)
const contactCount = faker.number.int({ min: 1, max: 3 });
for (let j = 0; j < contactCount; j++) {
const newContact = {
@@ -52,7 +52,7 @@ async function seedVendors() {
await db.insert(vendorContacts).values(newContact);
}
- // 5) Possible Items 생성 (각 벤더당 2~5개)
+ // 5) Possible Items 생성 (각 협력업체당 2~5개)
// 여기서 "items" 테이블에서 랜덤 pick → vendorPossibleItems에 참조
const itemCount = faker.number.int({ min: 2, max: 5 });