feat: Phase 3 단가 테이블 마이그레이션 추가
- Phase 3.1: price_motor → items (SM) 누락 품목 13건 추가 - PM-020~PM-032: 제어기 (6P~100회선) - PM-033~PM-035: 방화/방범 콘트롤박스, 스위치 - Phase 3.2: price_raw_materials → items (RM) 누락 품목 4건 추가 - RM-007~RM-011: 신설비상문, 제연커튼, 화이바/와이어원단 - 중복 확인 로직: 기존 품목명과 mb_strtolower 비교 - 최종 결과: items 651건, prices 651건, BOM 18건 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
* Phase 1.2: chandj.item_list (9건) → items (PT), prices
|
||||
* Phase 2.1: chandj.BDmodels.seconditem → items (PT) 누락 부품 추가
|
||||
* Phase 2.2: chandj.BDmodels → items.bom JSON (FG ↔ PT 연결)
|
||||
* Phase 3.1: chandj.price_motor → items (SM) + prices 누락 품목
|
||||
* Phase 3.2: chandj.price_raw_materials → items (RM) + prices 누락 품목
|
||||
*
|
||||
* @see docs/plans/kd-items-migration-plan.md
|
||||
*/
|
||||
@@ -71,11 +73,25 @@ public function run(): void
|
||||
// Phase 2.2: BDmodels → items.bom JSON
|
||||
$bomCount = $this->migrateBom($tenantId);
|
||||
|
||||
$totalItems = $itemCount + $modelCount + $itemListCount + $bdPartsCount;
|
||||
// Phase 3.1: price_motor → items (SM) + prices
|
||||
$motorResult = $this->migratePriceMotor($tenantId, $userId);
|
||||
|
||||
// Phase 3.2: price_raw_materials → items (RM) + prices
|
||||
$rawMatResult = $this->migratePriceRawMaterials($tenantId, $userId);
|
||||
|
||||
$totalItems = $itemCount + $modelCount + $itemListCount + $bdPartsCount + $motorResult['items'] + $rawMatResult['items'];
|
||||
$totalPrices = $priceCount + $motorResult['prices'] + $rawMatResult['prices'];
|
||||
|
||||
$this->command->info('');
|
||||
$this->command->info('✅ 마이그레이션 완료:');
|
||||
$this->command->info(" → items: {$totalItems}건 (KDunitprice {$itemCount} + models {$modelCount} + item_list {$itemListCount} + BDmodels부품 {$bdPartsCount})");
|
||||
$this->command->info(" → prices: {$priceCount}건");
|
||||
$this->command->info(" → items: {$totalItems}건");
|
||||
$this->command->info(" - KDunitprice: {$itemCount}건");
|
||||
$this->command->info(" - models: {$modelCount}건");
|
||||
$this->command->info(" - item_list: {$itemListCount}건");
|
||||
$this->command->info(" - BDmodels부품: {$bdPartsCount}건");
|
||||
$this->command->info(" - price_motor: {$motorResult['items']}건");
|
||||
$this->command->info(" - price_raw_materials: {$rawMatResult['items']}건");
|
||||
$this->command->info(" → prices: {$totalPrices}건");
|
||||
$this->command->info(" → BOM 연결: {$bomCount}건");
|
||||
}
|
||||
|
||||
@@ -580,4 +596,316 @@ private function migrateBom(int $tenantId): int
|
||||
|
||||
return $updatedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 3.1: price_motor → items (SM) + prices
|
||||
*
|
||||
* price_motor JSON에서 누락된 품목만 추가
|
||||
* - 제어기, 방화/방범 콘트롤박스, 스위치, 리모콘 등
|
||||
*
|
||||
* @return array{items: int, prices: int}
|
||||
*/
|
||||
private function migratePriceMotor(int $tenantId, int $userId): array
|
||||
{
|
||||
$this->command->info('');
|
||||
$this->command->info('📦 [Phase 3.1] price_motor → items (SM) 누락 품목...');
|
||||
|
||||
// 최신 price_motor 데이터 조회
|
||||
$priceMotor = DB::connection('chandj')
|
||||
->table('price_motor')
|
||||
->where(function ($q) {
|
||||
$q->where('is_deleted', 0)->orWhereNull('is_deleted');
|
||||
})
|
||||
->orderByDesc('registedate')
|
||||
->first();
|
||||
|
||||
if (! $priceMotor || empty($priceMotor->itemList)) {
|
||||
$this->command->info(' → 소스 데이터 없음');
|
||||
|
||||
return ['items' => 0, 'prices' => 0];
|
||||
}
|
||||
|
||||
$itemList = json_decode($priceMotor->itemList, true);
|
||||
if (! is_array($itemList)) {
|
||||
$this->command->info(' → JSON 파싱 실패');
|
||||
|
||||
return ['items' => 0, 'prices' => 0];
|
||||
}
|
||||
|
||||
// 기존 items 이름 조회 (중복 체크용)
|
||||
$existingNames = DB::table('items')
|
||||
->where('tenant_id', $tenantId)
|
||||
->pluck('name')
|
||||
->map(fn ($n) => mb_strtolower($n))
|
||||
->toArray();
|
||||
|
||||
$items = [];
|
||||
$now = now();
|
||||
$newItemCodes = [];
|
||||
|
||||
foreach ($itemList as $idx => $item) {
|
||||
$col1 = $item['col1'] ?? ''; // 전압/카테고리 (220, 380, 제어기, 방화, 방범)
|
||||
$col2 = $item['col2'] ?? ''; // 용량/품목명
|
||||
$salesPrice = (float) str_replace(',', '', $item['col13'] ?? '0');
|
||||
|
||||
// 모터 품목은 KDunitprice에 이미 있으므로 스킵
|
||||
if (in_array($col1, ['220', '380'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 품목명 생성
|
||||
$name = trim("{$col1} {$col2}");
|
||||
if (empty($name) || $name === ' ') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 이미 존재하는 품목 스킵 (유사 이름 체크)
|
||||
$nameLower = mb_strtolower($name);
|
||||
$exists = false;
|
||||
foreach ($existingNames as $existingName) {
|
||||
if (str_contains($existingName, $nameLower) || str_contains($nameLower, $existingName)) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 코드 생성
|
||||
$code = 'PM-'.str_pad($idx + 1, 3, '0', STR_PAD_LEFT);
|
||||
|
||||
$items[] = [
|
||||
'tenant_id' => $tenantId,
|
||||
'item_type' => 'SM',
|
||||
'code' => $code,
|
||||
'name' => $name,
|
||||
'unit' => 'EA',
|
||||
'category_id' => null,
|
||||
'process_type' => null,
|
||||
'item_category' => null,
|
||||
'bom' => null,
|
||||
'attributes' => json_encode([
|
||||
'price_category' => $col1,
|
||||
'price_spec' => $col2,
|
||||
'legacy_source' => 'price_motor',
|
||||
]),
|
||||
'attributes_archive' => null,
|
||||
'options' => null,
|
||||
'description' => null,
|
||||
'is_active' => true,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
$newItemCodes[$code] = $salesPrice;
|
||||
$existingNames[] = $nameLower; // 중복 방지
|
||||
}
|
||||
|
||||
if (! empty($items)) {
|
||||
DB::table('items')->insert($items);
|
||||
}
|
||||
|
||||
// prices 생성
|
||||
$priceCount = 0;
|
||||
if (! empty($newItemCodes)) {
|
||||
$newItems = DB::table('items')
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereIn('code', array_keys($newItemCodes))
|
||||
->get(['id', 'code', 'item_type']);
|
||||
|
||||
$prices = [];
|
||||
foreach ($newItems as $item) {
|
||||
$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' => $newItemCodes[$item->code],
|
||||
'rounding_rule' => 'round',
|
||||
'rounding_unit' => 1,
|
||||
'supplier' => null,
|
||||
'effective_from' => $priceMotor->registedate ?? now()->toDateString(),
|
||||
'effective_to' => null,
|
||||
'note' => 'price_motor 마이그레이션',
|
||||
'status' => 'active',
|
||||
'is_final' => false,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
|
||||
if (! empty($prices)) {
|
||||
DB::table('prices')->insert($prices);
|
||||
$priceCount = count($prices);
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info(' → 소스 데이터: '.count($itemList).'건 (누락 '.count($items).'건 추가)');
|
||||
$this->command->info(' ✓ items: '.count($items).'건, prices: '.$priceCount.'건 생성 완료');
|
||||
|
||||
return ['items' => count($items), 'prices' => $priceCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 3.2: price_raw_materials → items (RM) + prices
|
||||
*
|
||||
* price_raw_materials JSON에서 누락된 원자재 품목 추가
|
||||
*
|
||||
* @return array{items: int, prices: int}
|
||||
*/
|
||||
private function migratePriceRawMaterials(int $tenantId, int $userId): array
|
||||
{
|
||||
$this->command->info('');
|
||||
$this->command->info('📦 [Phase 3.2] price_raw_materials → items (RM) 누락 품목...');
|
||||
|
||||
// 최신 price_raw_materials 데이터 조회
|
||||
$priceRaw = DB::connection('chandj')
|
||||
->table('price_raw_materials')
|
||||
->where(function ($q) {
|
||||
$q->where('is_deleted', 0)->orWhereNull('is_deleted');
|
||||
})
|
||||
->orderByDesc('registedate')
|
||||
->first();
|
||||
|
||||
if (! $priceRaw || empty($priceRaw->itemList)) {
|
||||
$this->command->info(' → 소스 데이터 없음');
|
||||
|
||||
return ['items' => 0, 'prices' => 0];
|
||||
}
|
||||
|
||||
$itemList = json_decode($priceRaw->itemList, true);
|
||||
if (! is_array($itemList)) {
|
||||
$this->command->info(' → JSON 파싱 실패');
|
||||
|
||||
return ['items' => 0, 'prices' => 0];
|
||||
}
|
||||
|
||||
// 기존 items 이름 조회 (중복 체크용)
|
||||
$existingNames = DB::table('items')
|
||||
->where('tenant_id', $tenantId)
|
||||
->pluck('name')
|
||||
->map(fn ($n) => mb_strtolower($n))
|
||||
->toArray();
|
||||
|
||||
$items = [];
|
||||
$now = now();
|
||||
$newItemCodes = [];
|
||||
|
||||
foreach ($itemList as $idx => $item) {
|
||||
$col1 = $item['col1'] ?? ''; // 카테고리 (슬랫, 스크린)
|
||||
$col2 = $item['col2'] ?? ''; // 품목명 (방화, 실리카, 화이바)
|
||||
$salesPrice = (float) str_replace(',', '', $item['col13'] ?? '0');
|
||||
|
||||
// 품목명 생성
|
||||
$name = trim("{$col1} {$col2}");
|
||||
if (empty($name) || $name === ' ') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 이미 존재하는 품목 스킵
|
||||
$nameLower = mb_strtolower($name);
|
||||
$exists = false;
|
||||
foreach ($existingNames as $existingName) {
|
||||
// 정확히 일치하거나 유사한 이름 체크
|
||||
$col2Lower = mb_strtolower($col2);
|
||||
if (str_contains($existingName, $col2Lower) || $existingName === $nameLower) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 코드 생성
|
||||
$code = 'RM-'.str_pad($idx + 1, 3, '0', STR_PAD_LEFT);
|
||||
|
||||
$items[] = [
|
||||
'tenant_id' => $tenantId,
|
||||
'item_type' => 'RM',
|
||||
'code' => $code,
|
||||
'name' => $name,
|
||||
'unit' => 'EA',
|
||||
'category_id' => null,
|
||||
'process_type' => null,
|
||||
'item_category' => $col1,
|
||||
'bom' => null,
|
||||
'attributes' => json_encode([
|
||||
'raw_category' => $col1,
|
||||
'raw_name' => $col2,
|
||||
'legacy_source' => 'price_raw_materials',
|
||||
]),
|
||||
'attributes_archive' => null,
|
||||
'options' => null,
|
||||
'description' => null,
|
||||
'is_active' => true,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
$newItemCodes[$code] = $salesPrice;
|
||||
$existingNames[] = $nameLower;
|
||||
}
|
||||
|
||||
if (! empty($items)) {
|
||||
DB::table('items')->insert($items);
|
||||
}
|
||||
|
||||
// prices 생성
|
||||
$priceCount = 0;
|
||||
if (! empty($newItemCodes)) {
|
||||
$newItems = DB::table('items')
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereIn('code', array_keys($newItemCodes))
|
||||
->get(['id', 'code', 'item_type']);
|
||||
|
||||
$prices = [];
|
||||
foreach ($newItems as $item) {
|
||||
$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' => $newItemCodes[$item->code],
|
||||
'rounding_rule' => 'round',
|
||||
'rounding_unit' => 1,
|
||||
'supplier' => null,
|
||||
'effective_from' => $priceRaw->registedate ?? now()->toDateString(),
|
||||
'effective_to' => null,
|
||||
'note' => 'price_raw_materials 마이그레이션',
|
||||
'status' => 'active',
|
||||
'is_final' => false,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
|
||||
if (! empty($prices)) {
|
||||
DB::table('prices')->insert($prices);
|
||||
$priceCount = count($prices);
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info(' → 소스 데이터: '.count($itemList).'건 (누락 '.count($items).'건 추가)');
|
||||
$this->command->info(' ✓ items: '.count($items).'건, prices: '.$priceCount.'건 생성 완료');
|
||||
|
||||
return ['items' => count($items), 'prices' => $priceCount];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user