'purchased_part', // 완제품 'PT' => 'bent_part', // 부품 'SM' => 'sub_material', // 부자재 'RM' => 'raw_material', // 원자재 'CS' => 'consumable', // 소모품 ]; public function run(): void { $this->command->info('Stock & Receiving Seeder 시작 (Tenant: '.self::TENANT_ID.')'); // Tenant 287의 품목 조회 $items = Item::withoutGlobalScopes() ->where('tenant_id', self::TENANT_ID) ->get(); if ($items->isEmpty()) { $this->command->warn('Tenant '.self::TENANT_ID.'에 품목이 없습니다.'); return; } $this->command->info('품목 수: '.$items->count()); // 각 품목에 대해 재고 및 입고 데이터 생성 foreach ($items as $item) { $this->createStockData($item); $this->createReceivingData($item); } $this->command->info('Stock & Receiving Seeder 완료'); } /** * 재고 데이터 생성 */ private function createStockData(Item $item): void { $stockType = self::ITEM_TYPE_MAP[$item->item_type] ?? 'raw_material'; // 랜덤 재고 수량 $stockQty = rand(50, 500); $safetyStock = rand(10, 50); $reservedQty = rand(0, min(20, $stockQty)); $availableQty = $stockQty - $reservedQty; // 재고 상태 결정 $status = 'normal'; if ($stockQty <= 0) { $status = 'out'; } elseif ($stockQty < $safetyStock) { $status = 'low'; } // 재고 레코드 생성 $stock = Stock::withoutGlobalScopes()->create([ 'tenant_id' => self::TENANT_ID, 'item_id' => $item->id, 'item_code' => $item->code, 'item_name' => $item->name, 'item_type' => $stockType, 'specification' => $item->attributes['specification'] ?? null, 'unit' => $item->unit, 'stock_qty' => $stockQty, 'safety_stock' => $safetyStock, 'reserved_qty' => $reservedQty, 'available_qty' => $availableQty, 'lot_count' => rand(1, 3), 'oldest_lot_date' => now()->subDays(rand(30, 180)), 'location' => $this->randomLocation(), 'status' => $status, 'last_receipt_date' => now()->subDays(rand(1, 30)), 'last_issue_date' => now()->subDays(rand(1, 14)), 'created_by' => self::USER_ID, ]); // LOT 데이터 생성 (1~3개) $lotCount = rand(1, 3); $remainingQty = $stockQty; for ($i = 0; $i < $lotCount && $remainingQty > 0; $i++) { $lotQty = $i === $lotCount - 1 ? $remainingQty : rand(10, $remainingQty); $remainingQty -= $lotQty; StockLot::withoutGlobalScopes()->create([ 'tenant_id' => self::TENANT_ID, 'stock_id' => $stock->id, 'lot_no' => 'LOT'.date('Ymd').'-'.str_pad($i + 1, 3, '0', STR_PAD_LEFT), 'supplier_lot' => 'SUP-'.rand(1000, 9999), 'qty' => $lotQty, 'reserved_qty' => 0, 'available_qty' => $lotQty, 'receipt_date' => now()->subDays(rand(1, 60)), 'fifo_order' => $i + 1, 'location' => $stock->location, 'status' => 'available', 'created_by' => self::USER_ID, ]); } $this->command->info(" - Stock 생성: {$item->code} ({$stockQty} {$item->unit})"); } /** * 입고 데이터 생성 */ private function createReceivingData(Item $item): void { // 각 품목당 2~4건의 입고 데이터 생성 $count = rand(2, 4); for ($i = 0; $i < $count; $i++) { $orderQty = rand(50, 200); $status = $this->randomReceivingStatus(); $receivingQty = $status === 'completed' ? $orderQty : ($status === 'receiving_pending' ? rand(1, $orderQty) : null); // 고유번호 생성을 위해 uniqid 사용 $uniqueId = substr(uniqid(), -6); Receiving::withoutGlobalScopes()->create([ 'tenant_id' => self::TENANT_ID, 'receiving_number' => 'RCV-'.date('Ymd').'-'.$uniqueId, 'order_no' => 'PO-'.date('Ymd').'-'.str_pad(rand(1, 999), 3, '0', STR_PAD_LEFT), 'order_date' => now()->subDays(rand(10, 60)), 'item_id' => $item->id, 'item_code' => $item->code, 'item_name' => $item->name, 'specification' => $item->attributes['specification'] ?? null, 'supplier' => $this->randomSupplier(), 'order_qty' => $orderQty, 'order_unit' => $item->unit, 'due_date' => now()->addDays(rand(-5, 14)), 'receiving_qty' => $receivingQty, 'receiving_date' => $status === 'completed' ? now()->subDays(rand(1, 10)) : null, 'lot_no' => $status === 'completed' ? 'LOT'.date('Ymd').'-'.str_pad(rand(1, 999), 3, '0', STR_PAD_LEFT) : null, 'supplier_lot' => $status === 'completed' ? 'SUP-'.rand(1000, 9999) : null, 'receiving_location' => $status === 'completed' ? $this->randomLocation() : null, 'receiving_manager' => $status === 'completed' ? '홍킬동' : null, 'status' => $status, 'remark' => $this->randomRemark(), 'created_by' => self::USER_ID, ]); } $this->command->info(" - Receiving 생성: {$item->code} ({$count}건)"); } /** * 랜덤 창고 위치 */ private function randomLocation(): string { $locations = ['A-01-01', 'A-01-02', 'A-02-01', 'B-01-01', 'B-02-01', 'C-01-01']; return $locations[array_rand($locations)]; } /** * 랜덤 공급업체 */ private function randomSupplier(): string { $suppliers = ['(주)대한철강', '삼성부품', 'LG전자', '현대산업', '한국소재', '동아물산']; return $suppliers[array_rand($suppliers)]; } /** * 랜덤 입고 상태 */ private function randomReceivingStatus(): string { $statuses = ['order_completed', 'shipping', 'inspection_pending', 'receiving_pending', 'completed']; $weights = [15, 15, 15, 15, 40]; // completed가 더 많이 $rand = rand(1, array_sum($weights)); $cumulative = 0; foreach ($statuses as $i => $status) { $cumulative += $weights[$i]; if ($rand <= $cumulative) { return $status; } } return 'completed'; } /** * 랜덤 비고 */ private function randomRemark(): ?string { $remarks = [ null, '긴급 발주', '정기 입고', '추가 발주', '품질 검사 필요', '납기 지연 주의', ]; return $remarks[array_rand($remarks)]; } }