- BadDebt 컨트롤러/서비스 기능 확장 - StockService 재고 조회 로직 개선 - ProcessReceivingRequest 검증 규칙 수정 - Item, Order, CommonCode, Shipment 모델 업데이트 - TodayIssueObserverService 개선 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
220 lines
6.0 KiB
PHP
220 lines
6.0 KiB
PHP
<?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',
|
|
'item_category', // 품목 카테고리 (SCREEN, STEEL, BENDING, ALUMINUM 등)
|
|
'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);
|
|
}
|
|
|
|
/**
|
|
* 재고 정보 (1:1)
|
|
*/
|
|
public function stock()
|
|
{
|
|
return $this->hasOne(\App\Models\Tenants\Stock::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);
|
|
}
|
|
|
|
/**
|
|
* 파일 (document_id/document_type 기반)
|
|
*
|
|
* ItemsFileController가 document_id=item_id, document_type='1'(group_id)로 저장
|
|
* morphMany(fileable) 대신 hasMany + where 조건 사용
|
|
*/
|
|
public function files()
|
|
{
|
|
return $this->hasMany(File::class, 'document_id')
|
|
->where('document_type', '1'); // ITEM_GROUP_ID
|
|
}
|
|
|
|
/**
|
|
* 태그 (폴리모픽)
|
|
*/
|
|
public function tags()
|
|
{
|
|
return $this->morphToMany(Tag::class, 'taggable');
|
|
}
|
|
|
|
// ──────────────────────────────────────────────────────────────
|
|
// 스코프
|
|
// ──────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 특정 타입 필터
|
|
* join 시 ambiguous 에러 방지를 위해 테이블 prefix 사용
|
|
*/
|
|
public function scopeType($query, string $type)
|
|
{
|
|
return $query->where('items.item_type', strtoupper($type));
|
|
}
|
|
|
|
/**
|
|
* Products 타입만 (FG, PT)
|
|
* join 시 ambiguous 에러 방지를 위해 테이블 prefix 사용
|
|
*/
|
|
public function scopeProducts($query)
|
|
{
|
|
return $query->whereIn('items.item_type', self::PRODUCT_TYPES);
|
|
}
|
|
|
|
/**
|
|
* Materials 타입만 (SM, RM, CS)
|
|
* join 시 ambiguous 에러 방지를 위해 테이블 prefix 사용
|
|
*/
|
|
public function scopeMaterials($query)
|
|
{
|
|
return $query->whereIn('items.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 자식 품목들 조회 및 모델에 설정
|
|
*/
|
|
public function loadBomChildren(): self
|
|
{
|
|
$childIds = $this->getBomChildIds();
|
|
if (empty($childIds)) {
|
|
$this->setRelation('bom_children', collect());
|
|
|
|
return $this;
|
|
}
|
|
|
|
$children = self::whereIn('id', $childIds)->get()->keyBy('id');
|
|
$this->setRelation('bom_children', $children);
|
|
|
|
return $this;
|
|
}
|
|
}
|