feat: [bending] 절곡품 전용 테이블 분리 API
- bending_items 전용 테이블 생성 (items.options → 정규 컬럼 승격) - bending_models 전용 테이블 생성 (가이드레일/케이스/하단마감재 통합) - bending_data JSON 통합 (별도 테이블 → bending_items.bending_data 컬럼) - bending_item_mappings 테이블 DROP (bending_items.code에 흡수) - BendingItemService/BendingCodeService → BendingItem 모델 전환 - GuiderailModelService component 이미지 자동 복사 - ItemsFileController bending_items/bending_models 폴백 지원 - Swagger 스키마 업데이트
This commit is contained in:
145
app/Console/Commands/BendingModelCopyImages.php
Normal file
145
app/Console/Commands/BendingModelCopyImages.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\BendingItem;
|
||||
use App\Models\BendingModel;
|
||||
use App\Models\Commons\File;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* 모델 component별 이미지 복사 (기초관리 원본 → 독립 복사본)
|
||||
*
|
||||
* component.source_num → bending_items.legacy_bending_id → 원본 이미지
|
||||
* → R2에 복사 → 새 file 레코드 → component.image_file_id 업데이트
|
||||
*
|
||||
* 실행: php artisan bending:model-copy-images [--dry-run] [--tenant_id=287]
|
||||
*/
|
||||
class BendingModelCopyImages extends Command
|
||||
{
|
||||
protected $signature = 'bending:model-copy-images
|
||||
{--tenant_id=287 : 테넌트 ID}
|
||||
{--dry-run : 미리보기}';
|
||||
|
||||
protected $description = '모델 component별 이미지를 기초관리에서 복사';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$tenantId = (int) $this->option('tenant_id');
|
||||
$dryRun = $this->option('dry-run');
|
||||
|
||||
// bending_items의 legacy_bending_id → 이미지 파일 매핑
|
||||
$itemImageMap = [];
|
||||
$items = BendingItem::where('tenant_id', $tenantId)
|
||||
->whereNotNull('legacy_bending_id')
|
||||
->get();
|
||||
|
||||
foreach ($items as $bi) {
|
||||
$file = File::where('document_type', 'bending_item')
|
||||
->where('document_id', $bi->id)
|
||||
->where('field_key', 'bending_diagram')
|
||||
->whereNull('deleted_at')
|
||||
->first();
|
||||
|
||||
if ($file) {
|
||||
$itemImageMap[$bi->legacy_bending_id] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("기초관리 이미지 매핑: " . count($itemImageMap) . "건");
|
||||
|
||||
$models = BendingModel::where('tenant_id', $tenantId)
|
||||
->whereNotNull('components')
|
||||
->get();
|
||||
|
||||
$copied = 0;
|
||||
$skipped = 0;
|
||||
$noSource = 0;
|
||||
|
||||
foreach ($models as $model) {
|
||||
$components = $model->components;
|
||||
if (empty($components)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$updated = false;
|
||||
foreach ($components as $idx => &$comp) {
|
||||
// 이미 image_file_id가 있으면 skip
|
||||
if (! empty($comp['image_file_id'])) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// source_num으로 기초관리 이미지 찾기
|
||||
$sourceNum = $comp['num'] ?? $comp['source_num'] ?? null;
|
||||
if (! $sourceNum) {
|
||||
$noSource++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$sourceFile = $itemImageMap[(int) $sourceNum] ?? null;
|
||||
if (! $sourceFile || ! $sourceFile->file_path) {
|
||||
$noSource++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
$this->line(" [DRY] model#{$model->id} comp[{$idx}] ← bending#{$sourceNum} file#{$sourceFile->id}");
|
||||
$copied++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// R2에서 파일 복사
|
||||
try {
|
||||
$newFile = $this->copyFile($sourceFile, $model->id, $tenantId);
|
||||
$comp['image_file_id'] = $newFile->id;
|
||||
$updated = true;
|
||||
$copied++;
|
||||
} catch (\Throwable $e) {
|
||||
$this->warn(" ⚠️ 복사 실패: model#{$model->id} comp[{$idx}] — {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
unset($comp);
|
||||
|
||||
if ($updated && ! $dryRun) {
|
||||
$model->components = $components;
|
||||
$model->save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info("완료: 복사 {$copied}건, 스킵 {$skipped}건, 소스없음 {$noSource}건");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function copyFile(File $source, int $modelId, int $tenantId): File
|
||||
{
|
||||
$extension = pathinfo($source->stored_name, PATHINFO_EXTENSION);
|
||||
$storedName = bin2hex(random_bytes(8)) . '.' . $extension;
|
||||
$directory = sprintf('%d/bending/model-parts/%s/%s', $tenantId, date('Y'), date('m'));
|
||||
$newPath = $directory . '/' . $storedName;
|
||||
|
||||
// R2 파일 복사
|
||||
$content = Storage::disk('r2')->get($source->file_path);
|
||||
Storage::disk('r2')->put($newPath, $content);
|
||||
|
||||
// 새 파일 레코드 생성
|
||||
return File::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'display_name' => $source->display_name,
|
||||
'stored_name' => $storedName,
|
||||
'file_path' => $newPath,
|
||||
'file_size' => $source->file_size,
|
||||
'mime_type' => $source->mime_type,
|
||||
'file_type' => 'image',
|
||||
'field_key' => 'component_image',
|
||||
'document_id' => $modelId,
|
||||
'document_type' => 'bending_model',
|
||||
'is_temp' => false,
|
||||
'uploaded_by' => 1,
|
||||
'created_by' => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user