- Migrate5130Bom: 완제품 BOM 템플릿 마이그레이션 (61건) - Migrate5130Orders: 주문 데이터 마이그레이션 - Migrate5130PriceItems: 품목 데이터 마이그레이션 - Verify5130Calculation: 견적 계산 검증 커맨드 - Legacy5130Calculator: 레거시 계산 헬퍼 - ContractFromBiddingRequest: 입찰→계약 전환 요청 - 마이그레이션: shipments.work_order_id, order_id_mappings 테이블
788 lines
27 KiB
PHP
788 lines
27 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Attributes\AsCommand;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
#[AsCommand(name: 'migrate:5130-orders', description: '5130 수주 데이터를 SAM orders/order_items 테이블로 마이그레이션')]
|
|
class Migrate5130Orders extends Command
|
|
{
|
|
protected $signature = 'migrate:5130-orders
|
|
{--tenant_id=287 : Target tenant ID (default: 287 경동기업)}
|
|
{--dry-run : 실제 저장 없이 시뮬레이션만 수행}
|
|
{--step=all : 실행할 단계 (all|orders|items|extra)}
|
|
{--rollback : 마이그레이션 롤백 (source=5130 데이터 삭제)}
|
|
{--limit=0 : 처리할 최대 건수 (0=전체)}
|
|
{--offset=0 : 시작 위치}';
|
|
|
|
// 5130 DB 연결
|
|
private string $sourceDb = 'chandj';
|
|
|
|
// SAM DB 연결
|
|
private string $targetDb = 'mysql';
|
|
|
|
// 매핑 캐시 (source_num => order_id)
|
|
private array $orderMappings = [];
|
|
|
|
// 통계
|
|
private array $stats = [
|
|
'orders' => 0,
|
|
'order_items_screen' => 0,
|
|
'order_items_slat' => 0,
|
|
'order_items_motor' => 0,
|
|
'order_items_bend' => 0,
|
|
'order_items_etc' => 0,
|
|
'skipped' => 0,
|
|
'errors' => 0,
|
|
];
|
|
|
|
public function handle(): int
|
|
{
|
|
$tenantId = (int) $this->option('tenant_id');
|
|
$dryRun = $this->option('dry-run');
|
|
$step = $this->option('step');
|
|
$rollback = $this->option('rollback');
|
|
$limit = (int) $this->option('limit');
|
|
$offset = (int) $this->option('offset');
|
|
|
|
$this->info('=== 5130 → SAM 수주 마이그레이션 ===');
|
|
$this->info("Tenant ID: {$tenantId}");
|
|
$this->info('Mode: '.($dryRun ? 'DRY-RUN (시뮬레이션)' : 'LIVE'));
|
|
$this->info("Step: {$step}");
|
|
if ($limit > 0) {
|
|
$this->info("Limit: {$limit}, Offset: {$offset}");
|
|
}
|
|
$this->newLine();
|
|
|
|
if ($rollback) {
|
|
return $this->rollbackMigration($tenantId, $dryRun);
|
|
}
|
|
|
|
// 기존 매핑 로드
|
|
$this->loadExistingMappings();
|
|
|
|
$steps = $step === 'all'
|
|
? ['orders', 'items', 'extra']
|
|
: [$step];
|
|
|
|
foreach ($steps as $currentStep) {
|
|
$this->line(">>> Step: {$currentStep}");
|
|
|
|
match ($currentStep) {
|
|
'orders' => $this->migrateOrders($tenantId, $dryRun, $limit, $offset),
|
|
'items' => $this->migrateOrderItems($tenantId, $dryRun, $limit, $offset),
|
|
'extra' => $this->migrateExtraItems($tenantId, $dryRun, $limit, $offset),
|
|
default => $this->error("Unknown step: {$currentStep}"),
|
|
};
|
|
|
|
$this->newLine();
|
|
}
|
|
|
|
$this->info('=== 마이그레이션 완료 ===');
|
|
$this->showSummary();
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* 기존 매핑 로드 (중복 방지)
|
|
*/
|
|
private function loadExistingMappings(): void
|
|
{
|
|
$mappings = DB::connection($this->targetDb)->table('order_id_mappings')->get();
|
|
|
|
foreach ($mappings as $mapping) {
|
|
$this->orderMappings[$mapping->source_num] = $mapping->order_id;
|
|
}
|
|
|
|
$this->line('Loaded '.count($this->orderMappings).' existing mappings');
|
|
}
|
|
|
|
/**
|
|
* output → orders 마이그레이션
|
|
*/
|
|
private function migrateOrders(int $tenantId, bool $dryRun, int $limit, int $offset): void
|
|
{
|
|
$this->info('Migrating output → orders...');
|
|
|
|
$query = DB::connection($this->sourceDb)->table('output')
|
|
->where('is_deleted', '!=', '1')
|
|
->orderBy('num');
|
|
|
|
if ($limit > 0) {
|
|
$query->skip($offset)->take($limit);
|
|
}
|
|
|
|
$outputs = $query->get();
|
|
|
|
$this->line("Found {$outputs->count()} orders");
|
|
|
|
$bar = $this->output->createProgressBar($outputs->count());
|
|
$bar->start();
|
|
|
|
foreach ($outputs as $output) {
|
|
// 이미 마이그레이션된 경우 스킵
|
|
if (isset($this->orderMappings[$output->num])) {
|
|
$this->stats['skipped']++;
|
|
$bar->advance();
|
|
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
$orderData = $this->mapOutputToOrder($output, $tenantId);
|
|
|
|
if (! $dryRun) {
|
|
$orderId = DB::connection($this->targetDb)->table('orders')->insertGetId($orderData);
|
|
|
|
// 매핑 기록
|
|
DB::connection($this->targetDb)->table('order_id_mappings')->insert([
|
|
'source_num' => $output->num,
|
|
'order_id' => $orderId,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
$this->orderMappings[$output->num] = $orderId;
|
|
}
|
|
|
|
$this->stats['orders']++;
|
|
} catch (\Exception $e) {
|
|
$this->stats['errors']++;
|
|
$this->newLine();
|
|
$this->error("Error migrating order {$output->num}: {$e->getMessage()}");
|
|
}
|
|
|
|
$bar->advance();
|
|
}
|
|
|
|
$bar->finish();
|
|
$this->newLine();
|
|
$this->info("Orders migration completed: {$this->stats['orders']} created");
|
|
}
|
|
|
|
/**
|
|
* output → order 데이터 매핑
|
|
*/
|
|
private function mapOutputToOrder(object $output, int $tenantId): array
|
|
{
|
|
// 상태 코드 매핑
|
|
$statusMap = [
|
|
'등록' => 'REGISTERED',
|
|
'진행' => 'IN_PROGRESS',
|
|
'출고' => 'SHIPPED',
|
|
'완료' => 'COMPLETED',
|
|
'취소' => 'CANCELLED',
|
|
];
|
|
|
|
$status = $statusMap[$output->regist_state ?? ''] ?? 'REGISTERED';
|
|
|
|
// 주문번호 생성 (5130 원본 번호 기반)
|
|
$orderNo = 'ORD-5130-'.str_pad($output->num, 6, '0', STR_PAD_LEFT);
|
|
|
|
// options JSON에 저장할 레거시 데이터
|
|
$options = [
|
|
'legacy_num' => $output->num,
|
|
'legacy_parent_num' => $output->parent_num ?? null,
|
|
'legacy_con_num' => $output->con_num ?? null,
|
|
'orderman' => $output->orderman ?? null,
|
|
'receiver' => $output->receiver ?? null,
|
|
'outputplace' => $output->outputplace ?? null,
|
|
'root' => $output->root ?? null,
|
|
'steel' => $output->steel ?? null,
|
|
'motor' => $output->motor ?? null,
|
|
'delivery' => $output->delivery ?? null,
|
|
'screen_state' => $output->screen_state ?? null,
|
|
'slat_state' => $output->slat_state ?? null,
|
|
'bend_state' => $output->bend_state ?? null,
|
|
'motor_state' => $output->motor_state ?? null,
|
|
'screen_su' => $output->screen_su ?? null,
|
|
'screen_m2' => $output->screen_m2 ?? null,
|
|
'slat_su' => $output->slat_su ?? null,
|
|
'slat_m2' => $output->slat_m2 ?? null,
|
|
'secondord' => $output->secondord ?? null,
|
|
'secondordman' => $output->secondordman ?? null,
|
|
'secondordmantel' => $output->secondordmantel ?? null,
|
|
'secondordnum' => $output->secondordnum ?? null,
|
|
'prodCode' => $output->prodCode ?? null,
|
|
'warrantyNum' => $output->warrantyNum ?? null,
|
|
'warranty' => $output->warranty ?? null,
|
|
'lotNum' => $output->lotNum ?? null,
|
|
'pjnum' => $output->pjnum ?? null,
|
|
'major_category' => $output->major_category ?? null,
|
|
'position' => $output->position ?? null,
|
|
'makeWidth' => $output->makeWidth ?? null,
|
|
'makeHeight' => $output->makeHeight ?? null,
|
|
'maguriWing' => $output->maguriWing ?? null,
|
|
'ACI' => [
|
|
'regDate' => $output->ACIregDate ?? null,
|
|
'askDate' => $output->ACIaskDate ?? null,
|
|
'doneDate' => $output->ACIdoneDate ?? null,
|
|
'memo' => $output->ACImemo ?? null,
|
|
'check' => $output->ACIcheck ?? null,
|
|
'groupCode' => $output->ACIgroupCode ?? null,
|
|
'groupName' => $output->ACIgroupName ?? null,
|
|
],
|
|
'devMode' => $output->devMode ?? null,
|
|
'displayText' => $output->displayText ?? null,
|
|
'slatcheck' => $output->slatcheck ?? null,
|
|
'partscheck' => $output->partscheck ?? null,
|
|
'requestBendingASSY' => $output->requestBendingASSY ?? null,
|
|
'source' => '5130',
|
|
];
|
|
|
|
return [
|
|
'tenant_id' => $tenantId,
|
|
'order_no' => $orderNo,
|
|
'order_type_code' => 'STANDARD',
|
|
'status_code' => $status,
|
|
'category_code' => $output->major_category ?? null,
|
|
'received_at' => $this->parseDate($output->outdate),
|
|
'client_id' => null, // client 매핑은 별도 처리 필요
|
|
'client_name' => null,
|
|
'client_contact' => $output->phone ?? null,
|
|
'site_name' => $output->outworkplace ?? null,
|
|
'supply_amount' => $this->parseNumber($output->estimateTotal ?? '0'),
|
|
'tax_amount' => 0,
|
|
'total_amount' => $this->parseNumber($output->estimateTotal ?? '0'),
|
|
'discount_rate' => 0,
|
|
'discount_amount' => 0,
|
|
'delivery_date' => $this->parseDate($output->indate),
|
|
'memo' => $output->comment ?? null,
|
|
'remarks' => $output->updatecomment ?? null,
|
|
'options' => json_encode($options),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* screenlist/slatlist → order_items 마이그레이션
|
|
* 이미 마이그레이션된 orders에 대해서만 처리
|
|
*/
|
|
private function migrateOrderItems(int $tenantId, bool $dryRun, int $limit, int $offset): void
|
|
{
|
|
$this->info('Migrating screenlist/slatlist → order_items...');
|
|
|
|
// 매핑된 source_num 목록으로 필터링
|
|
$sourceNums = array_keys($this->orderMappings);
|
|
|
|
if (empty($sourceNums)) {
|
|
$this->warn('No order mappings found. Run orders step first.');
|
|
|
|
return;
|
|
}
|
|
|
|
$query = DB::connection($this->sourceDb)->table('output')
|
|
->whereIn('num', $sourceNums)
|
|
->where(function ($q) {
|
|
$q->whereRaw('LENGTH(screenlist) > 2') // '{}' 보다 긴 것만
|
|
->orWhereRaw('LENGTH(slatlist) > 2');
|
|
})
|
|
->orderBy('num');
|
|
|
|
$outputs = $query->get();
|
|
|
|
$this->line("Found {$outputs->count()} orders with screen/slat items");
|
|
|
|
if ($outputs->isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
$bar = $this->output->createProgressBar($outputs->count());
|
|
$bar->start();
|
|
|
|
foreach ($outputs as $output) {
|
|
$orderId = $this->orderMappings[$output->num] ?? null;
|
|
|
|
if (! $orderId) {
|
|
$bar->advance();
|
|
|
|
continue;
|
|
}
|
|
|
|
// 이미 order_items가 있는지 확인 (중복 방지)
|
|
$existingCount = DB::connection($this->targetDb)->table('order_items')
|
|
->where('order_id', $orderId)
|
|
->count();
|
|
|
|
if ($existingCount > 0) {
|
|
$bar->advance();
|
|
|
|
continue;
|
|
}
|
|
|
|
// screenlist 처리
|
|
if (! empty($output->screenlist) && strlen($output->screenlist) > 2) {
|
|
$this->processJsonItems($output->screenlist, $orderId, $tenantId, 'SCREEN', $dryRun);
|
|
}
|
|
|
|
// slatlist 처리
|
|
if (! empty($output->slatlist) && strlen($output->slatlist) > 2) {
|
|
$this->processJsonItems($output->slatlist, $orderId, $tenantId, 'SLAT', $dryRun);
|
|
}
|
|
|
|
$bar->advance();
|
|
}
|
|
|
|
$bar->finish();
|
|
$this->newLine();
|
|
$this->info("Order items migration completed: screen={$this->stats['order_items_screen']}, slat={$this->stats['order_items_slat']}");
|
|
}
|
|
|
|
/**
|
|
* JSON 리스트를 order_items로 변환
|
|
*/
|
|
private function processJsonItems(string $json, int $orderId, int $tenantId, string $type, bool $dryRun): void
|
|
{
|
|
$items = json_decode($json, true);
|
|
|
|
if (! is_array($items)) {
|
|
return;
|
|
}
|
|
|
|
$serialNo = 0;
|
|
foreach ($items as $item) {
|
|
$serialNo++;
|
|
|
|
// 빈 항목 스킵
|
|
if (empty($item['cutwidth']) && empty($item['cutheight']) && empty($item['text1'])) {
|
|
continue;
|
|
}
|
|
|
|
$specification = '';
|
|
if (! empty($item['cutwidth']) || ! empty($item['cutheight'])) {
|
|
$specification = ($item['cutwidth'] ?? '').'x'.($item['cutheight'] ?? '');
|
|
}
|
|
|
|
$itemData = [
|
|
'tenant_id' => $tenantId,
|
|
'order_id' => $orderId,
|
|
'serial_no' => $serialNo,
|
|
'item_code' => $type.'-'.str_pad($serialNo, 3, '0', STR_PAD_LEFT),
|
|
'item_name' => $type === 'SCREEN' ? '방충망' : '슬랫',
|
|
'specification' => $specification,
|
|
'floor_code' => $item['floors'] ?? null,
|
|
'symbol_code' => $item['text1'] ?? null,
|
|
'unit' => 'EA',
|
|
'quantity' => (int) ($item['number'] ?? 1),
|
|
'unit_price' => 0,
|
|
'supply_amount' => 0,
|
|
'tax_amount' => 0,
|
|
'total_amount' => 0,
|
|
'status_code' => 'PENDING',
|
|
'remarks' => $item['memo'] ?? null,
|
|
'note' => $item['text2'] ?? null,
|
|
'sort_order' => $serialNo,
|
|
'attributes' => json_encode([
|
|
'item_type' => $type,
|
|
'cutwidth' => $item['cutwidth'] ?? null,
|
|
'cutheight' => $item['cutheight'] ?? null,
|
|
'exititem' => $item['exititem'] ?? null,
|
|
'printside' => $item['printside'] ?? null,
|
|
'direction' => $item['direction'] ?? null,
|
|
'intervalnum' => $item['intervalnum'] ?? null,
|
|
'intervalnumsecond' => $item['intervalnumsecond'] ?? null,
|
|
'exitinterval' => $item['exitinterval'] ?? null,
|
|
'cover' => $item['cover'] ?? null,
|
|
'drawbottom1' => $item['drawbottom1'] ?? null,
|
|
'drawbottom2' => $item['drawbottom2'] ?? null,
|
|
'drawbottom3' => $item['drawbottom3'] ?? null,
|
|
'draw' => $item['draw'] ?? null,
|
|
'done_check' => $item['done_check'] ?? null,
|
|
'remain_check' => $item['remain_check'] ?? null,
|
|
'mid_check' => $item['mid_check'] ?? null,
|
|
'left_check' => $item['left_check'] ?? null,
|
|
'right_check' => $item['right_check'] ?? null,
|
|
// SLAT 전용
|
|
'hinge' => $item['hinge'] ?? null,
|
|
'hingenum' => $item['hingenum'] ?? null,
|
|
'hinge_direction' => $item['hinge_direction'] ?? null,
|
|
'source' => '5130',
|
|
]),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
if (! $dryRun) {
|
|
DB::connection($this->targetDb)->table('order_items')->insert($itemData);
|
|
}
|
|
|
|
if ($type === 'SCREEN') {
|
|
$this->stats['order_items_screen']++;
|
|
} else {
|
|
$this->stats['order_items_slat']++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* output_extra (motorList, bendList 등) → order_items 마이그레이션
|
|
*/
|
|
private function migrateExtraItems(int $tenantId, bool $dryRun, int $limit, int $offset): void
|
|
{
|
|
$this->info('Migrating output_extra → order_items (motor, bend, etc)...');
|
|
|
|
$query = DB::connection($this->sourceDb)->table('output_extra')
|
|
->orderBy('parent_num');
|
|
|
|
if ($limit > 0) {
|
|
$query->skip($offset)->take($limit);
|
|
}
|
|
|
|
$extras = $query->get();
|
|
|
|
$this->line("Found {$extras->count()} output_extra records");
|
|
|
|
$bar = $this->output->createProgressBar($extras->count());
|
|
$bar->start();
|
|
|
|
foreach ($extras as $extra) {
|
|
$orderId = $this->orderMappings[$extra->parent_num] ?? null;
|
|
|
|
if (! $orderId) {
|
|
$bar->advance();
|
|
|
|
continue;
|
|
}
|
|
|
|
// motorList 처리
|
|
if (! empty($extra->motorList) && $extra->motorList !== '{}') {
|
|
$this->processMotorList($extra->motorList, $orderId, $tenantId, $dryRun);
|
|
}
|
|
|
|
// bendList 처리
|
|
if (! empty($extra->bendList) && $extra->bendList !== '{}') {
|
|
$this->processBendList($extra->bendList, $orderId, $tenantId, $dryRun);
|
|
}
|
|
|
|
// etcList 처리
|
|
if (! empty($extra->etcList) && $extra->etcList !== '{}') {
|
|
$this->processEtcList($extra->etcList, $orderId, $tenantId, $dryRun);
|
|
}
|
|
|
|
// orders.options 업데이트 (금액 정보)
|
|
if (! $dryRun) {
|
|
$this->updateOrderOptions($orderId, $extra);
|
|
}
|
|
|
|
$bar->advance();
|
|
}
|
|
|
|
$bar->finish();
|
|
$this->newLine();
|
|
$this->info("Extra items migration completed: motor={$this->stats['order_items_motor']}, bend={$this->stats['order_items_bend']}, etc={$this->stats['order_items_etc']}");
|
|
}
|
|
|
|
/**
|
|
* motorList → order_items (MOTOR)
|
|
*/
|
|
private function processMotorList(string $json, int $orderId, int $tenantId, bool $dryRun): void
|
|
{
|
|
$items = json_decode($json, true);
|
|
|
|
if (! is_array($items)) {
|
|
return;
|
|
}
|
|
|
|
$serialNo = $this->getNextSerialNo($orderId);
|
|
|
|
foreach ($items as $item) {
|
|
if (empty($item['col1'])) {
|
|
continue;
|
|
}
|
|
|
|
$itemData = [
|
|
'tenant_id' => $tenantId,
|
|
'order_id' => $orderId,
|
|
'serial_no' => $serialNo++,
|
|
'item_code' => 'MOTOR-'.str_pad($serialNo, 3, '0', STR_PAD_LEFT),
|
|
'item_name' => $item['col1'] ?? '모터',
|
|
'specification' => $item['col2'] ?? null, // 용량
|
|
'unit' => 'EA',
|
|
'quantity' => (int) ($item['col5'] ?? 1),
|
|
'unit_price' => 0,
|
|
'supply_amount' => 0,
|
|
'tax_amount' => 0,
|
|
'total_amount' => 0,
|
|
'status_code' => 'PENDING',
|
|
'sort_order' => $serialNo,
|
|
'attributes' => json_encode([
|
|
'item_type' => 'MOTOR',
|
|
'dimension' => $item['col3'] ?? null, // 규격
|
|
'inch' => $item['col4'] ?? null,
|
|
'type' => $item['col6'] ?? null, // 형태
|
|
'option' => $item['col7'] ?? null,
|
|
'power' => $item['col8'] ?? null, // 전원
|
|
'source' => '5130',
|
|
]),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
if (! $dryRun) {
|
|
DB::connection($this->targetDb)->table('order_items')->insert($itemData);
|
|
}
|
|
|
|
$this->stats['order_items_motor']++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* bendList → order_items (BEND)
|
|
*/
|
|
private function processBendList(string $json, int $orderId, int $tenantId, bool $dryRun): void
|
|
{
|
|
$items = json_decode($json, true);
|
|
|
|
if (! is_array($items)) {
|
|
return;
|
|
}
|
|
|
|
$serialNo = $this->getNextSerialNo($orderId);
|
|
|
|
foreach ($items as $item) {
|
|
if (empty($item['col1'])) {
|
|
continue;
|
|
}
|
|
|
|
$itemData = [
|
|
'tenant_id' => $tenantId,
|
|
'order_id' => $orderId,
|
|
'serial_no' => $serialNo++,
|
|
'item_code' => 'BEND-'.str_pad($serialNo, 3, '0', STR_PAD_LEFT),
|
|
'item_name' => $item['col1'] ?? '절곡',
|
|
'specification' => $item['col2'] ?? null, // 재질
|
|
'unit' => 'EA',
|
|
'quantity' => (int) ($item['col7'] ?? 1),
|
|
'unit_price' => 0,
|
|
'supply_amount' => 0,
|
|
'tax_amount' => 0,
|
|
'total_amount' => 0,
|
|
'status_code' => 'PENDING',
|
|
'remarks' => $item['col8'] ?? null, // 비고
|
|
'sort_order' => $serialNo,
|
|
'attributes' => json_encode([
|
|
'item_type' => 'BEND',
|
|
'length' => $item['col3'] ?? null,
|
|
'width' => $item['col5'] ?? null,
|
|
'drawing' => $item['col6'] ?? null, // 도면이미지
|
|
'source' => '5130',
|
|
]),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
if (! $dryRun) {
|
|
DB::connection($this->targetDb)->table('order_items')->insert($itemData);
|
|
}
|
|
|
|
$this->stats['order_items_bend']++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* etcList → order_items (ETC)
|
|
*/
|
|
private function processEtcList(string $json, int $orderId, int $tenantId, bool $dryRun): void
|
|
{
|
|
$items = json_decode($json, true);
|
|
|
|
if (! is_array($items)) {
|
|
return;
|
|
}
|
|
|
|
$serialNo = $this->getNextSerialNo($orderId);
|
|
|
|
foreach ($items as $item) {
|
|
if (empty($item['col1'])) {
|
|
continue;
|
|
}
|
|
|
|
$itemData = [
|
|
'tenant_id' => $tenantId,
|
|
'order_id' => $orderId,
|
|
'serial_no' => $serialNo++,
|
|
'item_code' => 'ETC-'.str_pad($serialNo, 3, '0', STR_PAD_LEFT),
|
|
'item_name' => $item['col1'] ?? '기타',
|
|
'specification' => $item['col2'] ?? null,
|
|
'unit' => 'EA',
|
|
'quantity' => (int) ($item['col3'] ?? 1),
|
|
'unit_price' => $this->parseNumber($item['col4'] ?? '0'),
|
|
'supply_amount' => $this->parseNumber($item['col5'] ?? '0'),
|
|
'tax_amount' => 0,
|
|
'total_amount' => $this->parseNumber($item['col5'] ?? '0'),
|
|
'status_code' => 'PENDING',
|
|
'sort_order' => $serialNo,
|
|
'attributes' => json_encode([
|
|
'item_type' => 'ETC',
|
|
'source' => '5130',
|
|
]),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
if (! $dryRun) {
|
|
DB::connection($this->targetDb)->table('order_items')->insert($itemData);
|
|
}
|
|
|
|
$this->stats['order_items_etc']++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* order의 다음 serial_no 조회
|
|
*/
|
|
private function getNextSerialNo(int $orderId): int
|
|
{
|
|
$max = DB::connection($this->targetDb)->table('order_items')
|
|
->where('order_id', $orderId)
|
|
->max('serial_no');
|
|
|
|
return ($max ?? 0) + 1;
|
|
}
|
|
|
|
/**
|
|
* orders.options 업데이트 (output_extra 금액 정보)
|
|
*/
|
|
private function updateOrderOptions(int $orderId, object $extra): void
|
|
{
|
|
$order = DB::connection($this->targetDb)->table('orders')
|
|
->where('id', $orderId)
|
|
->first();
|
|
|
|
if (! $order) {
|
|
return;
|
|
}
|
|
|
|
$options = json_decode($order->options ?? '{}', true);
|
|
|
|
// output_extra 금액 정보 추가
|
|
$options['estimate_first'] = $this->parseNumber($extra->EstimateFirstSum ?? '0');
|
|
$options['estimate_update'] = $this->parseNumber($extra->EstimateUpdatetSum ?? '0');
|
|
$options['estimate_diff'] = $this->parseNumber($extra->EstimateDiffer ?? '0');
|
|
$options['estimate_quantity'] = $this->parseNumber($extra->estimateSurang ?? '0');
|
|
$options['inspection_fee'] = $this->parseNumber($extra->inspectionFee ?? '0');
|
|
|
|
// 금액 필드 업데이트
|
|
$supplyAmount = $this->parseNumber($extra->estimateTotal ?? '0');
|
|
$discountRate = $this->parseNumber($extra->EstimateDiscountRate ?? '0');
|
|
$discountAmount = $this->parseNumber($extra->EstimateDiscount ?? '0');
|
|
$totalAmount = $this->parseNumber($extra->EstimateFinalSum ?? '0');
|
|
|
|
DB::connection($this->targetDb)->table('orders')
|
|
->where('id', $orderId)
|
|
->update([
|
|
'supply_amount' => $supplyAmount ?: $order->supply_amount,
|
|
'discount_rate' => $discountRate,
|
|
'discount_amount' => $discountAmount,
|
|
'total_amount' => $totalAmount ?: $order->total_amount,
|
|
'options' => json_encode($options),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 롤백 (source=5130 데이터 삭제)
|
|
*/
|
|
private function rollbackMigration(int $tenantId, bool $dryRun): int
|
|
{
|
|
$this->warn('=== 롤백 모드 ===');
|
|
|
|
if (! $this->confirm('5130에서 마이그레이션된 모든 수주 데이터를 삭제하시겠습니까?')) {
|
|
$this->info('롤백 취소됨');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
// 1. order_items 삭제 (order_id 기준)
|
|
$orderIds = DB::connection($this->targetDb)->table('order_id_mappings')
|
|
->pluck('order_id')
|
|
->toArray();
|
|
|
|
if (! empty($orderIds)) {
|
|
$itemCount = DB::connection($this->targetDb)->table('order_items')
|
|
->whereIn('order_id', $orderIds)
|
|
->count();
|
|
|
|
if (! $dryRun) {
|
|
DB::connection($this->targetDb)->table('order_items')
|
|
->whereIn('order_id', $orderIds)
|
|
->delete();
|
|
}
|
|
$this->line("Deleted {$itemCount} order_items");
|
|
|
|
// 2. orders 삭제
|
|
if (! $dryRun) {
|
|
DB::connection($this->targetDb)->table('orders')
|
|
->whereIn('id', $orderIds)
|
|
->delete();
|
|
}
|
|
$this->line('Deleted '.count($orderIds).' orders');
|
|
}
|
|
|
|
// 3. order_id_mappings 삭제
|
|
if (! $dryRun) {
|
|
DB::connection($this->targetDb)->table('order_id_mappings')->truncate();
|
|
}
|
|
$this->line('Cleared order_id_mappings');
|
|
|
|
$this->info('롤백 완료');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* 날짜 파싱
|
|
*/
|
|
private function parseDate(?string $value): ?string
|
|
{
|
|
if (empty($value)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return date('Y-m-d H:i:s', strtotime($value));
|
|
} catch (\Exception $e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 숫자 파싱 (varchar → decimal)
|
|
*/
|
|
private function parseNumber(?string $value): float
|
|
{
|
|
if (empty($value)) {
|
|
return 0;
|
|
}
|
|
|
|
$cleaned = preg_replace('/[^\d.-]/', '', $value);
|
|
|
|
return is_numeric($cleaned) ? (float) $cleaned : 0;
|
|
}
|
|
|
|
/**
|
|
* 요약 출력
|
|
*/
|
|
private function showSummary(): void
|
|
{
|
|
$this->newLine();
|
|
$this->table(
|
|
['Category', 'Count'],
|
|
[
|
|
['Orders', $this->stats['orders']],
|
|
['Screen Items', $this->stats['order_items_screen']],
|
|
['Slat Items', $this->stats['order_items_slat']],
|
|
['Motor Items', $this->stats['order_items_motor']],
|
|
['Bend Items', $this->stats['order_items_bend']],
|
|
['Etc Items', $this->stats['order_items_etc']],
|
|
['Skipped', $this->stats['skipped']],
|
|
['Errors', $this->stats['errors']],
|
|
]
|
|
);
|
|
}
|
|
}
|