feat: Items 테이블 통합 마이그레이션 Phase 0-5 구현
## 주요 변경사항 - Phase 0: 비표준 item_type 데이터 정규화 마이그레이션 - Phase 1.1: items 테이블 생성 (products + materials 통합) - Phase 1.2: item_details 테이블 생성 (1:1 확장 필드) - Phase 1.3: 데이터 이관 + item_id_mappings 테이블 생성 - Phase 3: item_pages.source_table 업데이트 - Phase 5: 참조 테이블 마이그레이션 (product_components, orders 등) ## 신규 파일 - app/Models/Items/Item.php - 통합 아이템 모델 - app/Models/Items/ItemDetail.php - 1:1 확장 필드 모델 - app/Services/ItemService.php - 통합 서비스 클래스 ## 수정 파일 - ItemPage.php - items 테이블 지원 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
198
app/Models/Items/Item.php
Normal file
198
app/Models/Items/Item.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Items;
|
||||
|
||||
use App\Models\Commons\Category;
|
||||
use App\Models\Commons\File;
|
||||
use App\Models\Commons\Tag;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use App\Traits\ModelTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* 통합 품목 모델
|
||||
*
|
||||
* products + materials를 통합한 단일 품목 테이블
|
||||
* item_type: FG(완제품), PT(부품), SM(부자재), RM(원자재), CS(소모품)
|
||||
*/
|
||||
class Item extends Model
|
||||
{
|
||||
use BelongsToTenant, ModelTrait, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'item_type',
|
||||
'code',
|
||||
'name',
|
||||
'unit',
|
||||
'category_id',
|
||||
'bom',
|
||||
'attributes',
|
||||
'attributes_archive',
|
||||
'options',
|
||||
'description',
|
||||
'is_active',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'bom' => 'array',
|
||||
'attributes' => 'array',
|
||||
'attributes_archive' => 'array',
|
||||
'options' => 'array',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* item_type 상수
|
||||
*/
|
||||
public const TYPE_FINISHED_GOODS = 'FG'; // 완제품
|
||||
|
||||
public const TYPE_PARTS = 'PT'; // 부품
|
||||
|
||||
public const TYPE_SUB_MATERIALS = 'SM'; // 부자재
|
||||
|
||||
public const TYPE_RAW_MATERIALS = 'RM'; // 원자재
|
||||
|
||||
public const TYPE_CONSUMABLES = 'CS'; // 소모품
|
||||
|
||||
/**
|
||||
* Products 타입 (FG, PT)
|
||||
*/
|
||||
public const PRODUCT_TYPES = ['FG', 'PT'];
|
||||
|
||||
/**
|
||||
* Materials 타입 (SM, RM, CS)
|
||||
*/
|
||||
public const MATERIAL_TYPES = ['SM', 'RM', 'CS'];
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// 관계
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 상세 정보 (1:1)
|
||||
*/
|
||||
public function details()
|
||||
{
|
||||
return $this->hasOne(ItemDetail::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 카테고리
|
||||
*/
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(Category::class, 'category_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* BOM 자식 품목들 (JSON bom 필드 기반)
|
||||
* 주의: 이 관계는 bom JSON에서 child_item_id를 추출하여 조회
|
||||
*/
|
||||
public function bomChildren()
|
||||
{
|
||||
$childIds = collect($this->bom ?? [])->pluck('child_item_id')->filter()->toArray();
|
||||
|
||||
return self::whereIn('id', $childIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일 (폴리모픽)
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
return $this->morphMany(File::class, 'fileable');
|
||||
}
|
||||
|
||||
/**
|
||||
* 태그 (폴리모픽)
|
||||
*/
|
||||
public function tags()
|
||||
{
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// 스코프
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 특정 타입 필터
|
||||
*/
|
||||
public function scopeType($query, string $type)
|
||||
{
|
||||
return $query->where('item_type', strtoupper($type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Products 타입만 (FG, PT)
|
||||
*/
|
||||
public function scopeProducts($query)
|
||||
{
|
||||
return $query->whereIn('item_type', self::PRODUCT_TYPES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Materials 타입만 (SM, RM, CS)
|
||||
*/
|
||||
public function scopeMaterials($query)
|
||||
{
|
||||
return $query->whereIn('item_type', self::MATERIAL_TYPES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 활성 품목만
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// 헬퍼 메서드
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Product 타입인지 확인
|
||||
*/
|
||||
public function isProduct(): bool
|
||||
{
|
||||
return in_array($this->item_type, self::PRODUCT_TYPES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Material 타입인지 확인
|
||||
*/
|
||||
public function isMaterial(): bool
|
||||
{
|
||||
return in_array($this->item_type, self::MATERIAL_TYPES);
|
||||
}
|
||||
|
||||
/**
|
||||
* BOM 자식 품목 ID 목록 추출
|
||||
*/
|
||||
public function getBomChildIds(): array
|
||||
{
|
||||
return collect($this->bom ?? [])->pluck('child_item_id')->filter()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* BOM 자식 품목들 조회 (Eager Loading 최적화)
|
||||
*/
|
||||
public function loadBomChildren()
|
||||
{
|
||||
$childIds = $this->getBomChildIds();
|
||||
if (empty($childIds)) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return self::whereIn('id', $childIds)->get()->keyBy('id');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user