- migrateModels(): chandj.models → items (FG) 18건
- migrateItemList(): chandj.item_list → items (PT) 9건
- migratePrices(): 다양한 소스 단가 처리 로직 개선
- 코드 포맷: FG-{model}-{type}-{finish}, PT-{name}
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
386 lines
13 KiB
PHP
386 lines
13 KiB
PHP
<?php
|
|
|
|
namespace Database\Seeders\Kyungdong;
|
|
|
|
use Database\Seeders\DummyDataSeeder;
|
|
use Illuminate\Database\Seeder;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* 경동기업 품목/단가 마이그레이션 Seeder
|
|
*
|
|
* Phase 1.0: chandj.KDunitprice (601건) → items, prices
|
|
* Phase 1.1: chandj.models (18건) → items (FG), prices
|
|
* Phase 1.2: chandj.item_list (9건) → items (PT), prices
|
|
*
|
|
* @see docs/plans/kd-items-migration-plan.md
|
|
*/
|
|
class KyungdongItemSeeder extends Seeder
|
|
{
|
|
/**
|
|
* item_div → item_type 매핑
|
|
*/
|
|
private const ITEM_TYPE_MAP = [
|
|
'[제품]' => 'FG',
|
|
'[상품]' => 'FG',
|
|
'[반제품]' => 'PT',
|
|
'[부재료]' => 'SM',
|
|
'[원재료]' => 'RM',
|
|
'[무형상품]' => 'CS',
|
|
];
|
|
|
|
/**
|
|
* finishing_type 약어 매핑
|
|
*/
|
|
private const FINISHING_MAP = [
|
|
'SUS마감' => 'SUS',
|
|
'EGI마감' => 'EGI',
|
|
];
|
|
|
|
/**
|
|
* 경동기업 품목/단가 마이그레이션 실행
|
|
*/
|
|
public function run(): void
|
|
{
|
|
$tenantId = DummyDataSeeder::TENANT_ID;
|
|
$userId = DummyDataSeeder::USER_ID;
|
|
|
|
$this->command->info('🚀 경동기업 품목/단가 마이그레이션 시작...');
|
|
$this->command->info(" 대상 테넌트: ID {$tenantId}");
|
|
|
|
// 1. 기존 데이터 삭제
|
|
$this->cleanupExistingData($tenantId);
|
|
|
|
// Phase 1.0: KDunitprice → items
|
|
$itemCount = $this->migrateItems($tenantId, $userId);
|
|
|
|
// Phase 1.1: models → items (FG)
|
|
$modelCount = $this->migrateModels($tenantId, $userId);
|
|
|
|
// Phase 1.2: item_list → items (PT)
|
|
$itemListCount = $this->migrateItemList($tenantId, $userId);
|
|
|
|
// prices 생성 (모든 items 기반)
|
|
$priceCount = $this->migratePrices($tenantId, $userId);
|
|
|
|
$totalItems = $itemCount + $modelCount + $itemListCount;
|
|
$this->command->info('');
|
|
$this->command->info('✅ 마이그레이션 완료:');
|
|
$this->command->info(" → items: {$totalItems}건 (KDunitprice {$itemCount} + models {$modelCount} + item_list {$itemListCount})");
|
|
$this->command->info(" → prices: {$priceCount}건");
|
|
}
|
|
|
|
/**
|
|
* 기존 데이터 삭제 (tenant_id 기준)
|
|
*/
|
|
private function cleanupExistingData(int $tenantId): void
|
|
{
|
|
$this->command->info('');
|
|
$this->command->info('🧹 기존 데이터 삭제 중...');
|
|
|
|
// prices 먼저 삭제 (FK 관계)
|
|
$priceCount = DB::table('prices')->where('tenant_id', $tenantId)->count();
|
|
DB::table('prices')->where('tenant_id', $tenantId)->delete();
|
|
$this->command->info(" → prices: {$priceCount}건 삭제");
|
|
|
|
// items 삭제
|
|
$itemCount = DB::table('items')->where('tenant_id', $tenantId)->count();
|
|
DB::table('items')->where('tenant_id', $tenantId)->delete();
|
|
$this->command->info(" → items: {$itemCount}건 삭제");
|
|
}
|
|
|
|
/**
|
|
* KDunitprice → items 마이그레이션
|
|
*/
|
|
private function migrateItems(int $tenantId, int $userId): int
|
|
{
|
|
$this->command->info('');
|
|
$this->command->info('📦 KDunitprice → items 마이그레이션...');
|
|
|
|
// chandj.KDunitprice에서 데이터 조회 (is_deleted=NULL이 활성 상태)
|
|
$kdItems = DB::connection('chandj')
|
|
->table('KDunitprice')
|
|
->whereNull('is_deleted')
|
|
->whereNotNull('prodcode')
|
|
->where('prodcode', '!=', '')
|
|
->get();
|
|
|
|
$this->command->info(" → 소스 데이터: {$kdItems->count()}건");
|
|
|
|
$items = [];
|
|
$now = now();
|
|
$batchCount = 0;
|
|
|
|
foreach ($kdItems as $kd) {
|
|
$items[] = [
|
|
'tenant_id' => $tenantId,
|
|
'item_type' => $this->mapItemType($kd->item_div),
|
|
'code' => $kd->prodcode,
|
|
'name' => $kd->item_name,
|
|
'unit' => $kd->unit,
|
|
'category_id' => null,
|
|
'process_type' => null,
|
|
'item_category' => null,
|
|
'bom' => null,
|
|
'attributes' => json_encode([
|
|
'spec' => $kd->spec,
|
|
'item_div' => $kd->item_div,
|
|
'legacy_source' => 'KDunitprice',
|
|
'legacy_num' => $kd->num,
|
|
]),
|
|
'attributes_archive' => null,
|
|
'options' => null,
|
|
'description' => null,
|
|
'is_active' => true,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
|
|
// 500건씩 배치 INSERT
|
|
if (count($items) >= 500) {
|
|
DB::table('items')->insert($items);
|
|
$batchCount += count($items);
|
|
$this->command->info(" → {$batchCount}건 완료...");
|
|
$items = [];
|
|
}
|
|
}
|
|
|
|
// 남은 데이터 INSERT
|
|
if (! empty($items)) {
|
|
DB::table('items')->insert($items);
|
|
$batchCount += count($items);
|
|
}
|
|
|
|
$this->command->info(" ✓ items: {$batchCount}건 생성 완료");
|
|
|
|
return $batchCount;
|
|
}
|
|
|
|
/**
|
|
* items 기반 → prices 마이그레이션
|
|
*/
|
|
private function migratePrices(int $tenantId, int $userId): int
|
|
{
|
|
$this->command->info('');
|
|
$this->command->info('💰 items → prices 마이그레이션...');
|
|
|
|
// 생성된 items 조회
|
|
$items = DB::table('items')
|
|
->where('tenant_id', $tenantId)
|
|
->get(['id', 'code', 'item_type', 'attributes']);
|
|
|
|
// KDunitprice 단가 (code → unitprice)
|
|
$kdPrices = DB::connection('chandj')
|
|
->table('KDunitprice')
|
|
->whereNull('is_deleted')
|
|
->whereNotNull('prodcode')
|
|
->where('prodcode', '!=', '')
|
|
->pluck('unitprice', 'prodcode');
|
|
|
|
// item_list 단가 (item_name → col13)
|
|
$itemListPrices = DB::connection('chandj')
|
|
->table('item_list')
|
|
->pluck('col13', 'item_name');
|
|
|
|
$prices = [];
|
|
$now = now();
|
|
$batchCount = 0;
|
|
|
|
foreach ($items as $item) {
|
|
$attributes = json_decode($item->attributes, true) ?? [];
|
|
$legacySource = $attributes['legacy_source'] ?? '';
|
|
|
|
// 소스별 단가 결정
|
|
$unitPrice = match ($legacySource) {
|
|
'KDunitprice' => $kdPrices[$item->code] ?? 0,
|
|
'item_list' => $itemListPrices[$attributes['legacy_num'] ? $this->getItemListName($item->code) : ''] ?? $attributes['base_price'] ?? 0,
|
|
'models' => 0, // models는 단가 없음
|
|
default => 0,
|
|
};
|
|
|
|
// item_list의 경우 attributes에 저장된 base_price 사용
|
|
if ($legacySource === 'item_list' && isset($attributes['base_price'])) {
|
|
$unitPrice = $attributes['base_price'];
|
|
}
|
|
|
|
$prices[] = [
|
|
'tenant_id' => $tenantId,
|
|
'item_type_code' => $item->item_type,
|
|
'item_id' => $item->id,
|
|
'client_group_id' => null,
|
|
'purchase_price' => 0,
|
|
'processing_cost' => null,
|
|
'loss_rate' => null,
|
|
'margin_rate' => null,
|
|
'sales_price' => $unitPrice,
|
|
'rounding_rule' => 'round',
|
|
'rounding_unit' => 1,
|
|
'supplier' => null,
|
|
'effective_from' => now()->toDateString(),
|
|
'effective_to' => null,
|
|
'note' => "{$legacySource} 마이그레이션",
|
|
'status' => 'active',
|
|
'is_final' => false,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
|
|
// 500건씩 배치 INSERT
|
|
if (count($prices) >= 500) {
|
|
DB::table('prices')->insert($prices);
|
|
$batchCount += count($prices);
|
|
$this->command->info(" → {$batchCount}건 완료...");
|
|
$prices = [];
|
|
}
|
|
}
|
|
|
|
// 남은 데이터 INSERT
|
|
if (! empty($prices)) {
|
|
DB::table('prices')->insert($prices);
|
|
$batchCount += count($prices);
|
|
}
|
|
|
|
$this->command->info(" ✓ prices: {$batchCount}건 생성 완료");
|
|
|
|
return $batchCount;
|
|
}
|
|
|
|
/**
|
|
* PT-{name} 코드에서 name 추출
|
|
*/
|
|
private function getItemListName(string $code): string
|
|
{
|
|
return str_starts_with($code, 'PT-') ? substr($code, 3) : '';
|
|
}
|
|
|
|
/**
|
|
* Phase 1.1: models → items (FG) 마이그레이션
|
|
*/
|
|
private function migrateModels(int $tenantId, int $userId): int
|
|
{
|
|
$this->command->info('');
|
|
$this->command->info('📦 [Phase 1.1] models → items (FG) 마이그레이션...');
|
|
|
|
$models = DB::connection('chandj')
|
|
->table('models')
|
|
->where(function ($q) {
|
|
$q->where('is_deleted', 0)->orWhereNull('is_deleted');
|
|
})
|
|
->get();
|
|
|
|
$this->command->info(" → 소스 데이터: {$models->count()}건");
|
|
|
|
$items = [];
|
|
$now = now();
|
|
|
|
foreach ($models as $model) {
|
|
$finishingShort = self::FINISHING_MAP[$model->finishing_type] ?? 'STD';
|
|
$code = "FG-{$model->model_name}-{$model->guiderail_type}-{$finishingShort}";
|
|
$name = "{$model->model_name} {$model->major_category} {$model->finishing_type} {$model->guiderail_type}";
|
|
|
|
$items[] = [
|
|
'tenant_id' => $tenantId,
|
|
'item_type' => 'FG',
|
|
'code' => $code,
|
|
'name' => trim($name),
|
|
'unit' => 'EA',
|
|
'category_id' => null,
|
|
'process_type' => null,
|
|
'item_category' => $model->major_category,
|
|
'bom' => null,
|
|
'attributes' => json_encode([
|
|
'model_name' => $model->model_name,
|
|
'major_category' => $model->major_category,
|
|
'finishing_type' => $model->finishing_type,
|
|
'guiderail_type' => $model->guiderail_type,
|
|
'legacy_source' => 'models',
|
|
'legacy_model_id' => $model->model_id,
|
|
]),
|
|
'attributes_archive' => null,
|
|
'options' => null,
|
|
'description' => null,
|
|
'is_active' => true,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
}
|
|
|
|
if (! empty($items)) {
|
|
DB::table('items')->insert($items);
|
|
}
|
|
|
|
$this->command->info(" ✓ items (FG): {$models->count()}건 생성 완료");
|
|
|
|
return $models->count();
|
|
}
|
|
|
|
/**
|
|
* Phase 1.2: item_list → items (PT) 마이그레이션
|
|
*/
|
|
private function migrateItemList(int $tenantId, int $userId): int
|
|
{
|
|
$this->command->info('');
|
|
$this->command->info('📦 [Phase 1.2] item_list → items (PT) 마이그레이션...');
|
|
|
|
$itemList = DB::connection('chandj')
|
|
->table('item_list')
|
|
->get();
|
|
|
|
$this->command->info(" → 소스 데이터: {$itemList->count()}건");
|
|
|
|
$items = [];
|
|
$now = now();
|
|
|
|
foreach ($itemList as $item) {
|
|
$code = "PT-{$item->item_name}";
|
|
|
|
$items[] = [
|
|
'tenant_id' => $tenantId,
|
|
'item_type' => 'PT',
|
|
'code' => $code,
|
|
'name' => $item->item_name,
|
|
'unit' => 'EA',
|
|
'category_id' => null,
|
|
'process_type' => null,
|
|
'item_category' => null,
|
|
'bom' => null,
|
|
'attributes' => json_encode([
|
|
'base_price' => $item->col13,
|
|
'legacy_source' => 'item_list',
|
|
'legacy_num' => $item->num,
|
|
]),
|
|
'attributes_archive' => null,
|
|
'options' => null,
|
|
'description' => null,
|
|
'is_active' => true,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
}
|
|
|
|
if (! empty($items)) {
|
|
DB::table('items')->insert($items);
|
|
}
|
|
|
|
$this->command->info(" ✓ items (PT): {$itemList->count()}건 생성 완료");
|
|
|
|
return $itemList->count();
|
|
}
|
|
|
|
/**
|
|
* item_div → item_type 매핑
|
|
*/
|
|
private function mapItemType(?string $itemDiv): string
|
|
{
|
|
return self::ITEM_TYPE_MAP[$itemDiv] ?? 'SM';
|
|
}
|
|
}
|