Files
sam-api/app/Models/Items/Item.php
권혁성 09db0da43b feat(API): 부실채권, 재고, 입고 기능 개선
- BadDebt 컨트롤러/서비스 기능 확장
- StockService 재고 조회 로직 개선
- ProcessReceivingRequest 검증 규칙 수정
- Item, Order, CommonCode, Shipment 모델 업데이트
- TodayIssueObserverService 개선

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 21:32:23 +09:00

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;
}
}