'decimal:3', 'safety_stock' => 'decimal:3', 'reserved_qty' => 'decimal:3', 'available_qty' => 'decimal:3', 'lot_count' => 'integer', 'oldest_lot_date' => 'date', 'last_receipt_date' => 'date', 'last_issue_date' => 'date', ]; /** * 품목 유형 목록 */ public const ITEM_TYPES = [ 'raw_material' => '원자재', 'bent_part' => '절곡부품', 'purchased_part' => '구매부품', 'sub_material' => '부자재', 'consumable' => '소모품', ]; /** * 재고 상태 목록 */ public const STATUSES = [ 'normal' => '정상', 'low' => '부족', 'out' => '없음', ]; /** * 품목 관계 */ public function item(): BelongsTo { return $this->belongsTo(\App\Models\Items\Item::class); } /** * LOT 관계 */ public function lots(): HasMany { return $this->hasMany(StockLot::class)->orderBy('fifo_order'); } /** * 생성자 관계 */ public function creator(): BelongsTo { return $this->belongsTo(\App\Models\Members\User::class, 'created_by'); } /** * 품목유형 라벨 */ public function getItemTypeLabelAttribute(): string { return self::ITEM_TYPES[$this->item_type] ?? $this->item_type; } /** * 상태 라벨 */ public function getStatusLabelAttribute(): string { return self::STATUSES[$this->status] ?? $this->status; } /** * 경과일 계산 (가장 오래된 LOT 기준) */ public function getDaysElapsedAttribute(): int { if (! $this->oldest_lot_date) { return 0; } return $this->oldest_lot_date->diffInDays(now()); } /** * 재고 상태 계산 */ public function calculateStatus(): string { if ($this->stock_qty <= 0) { return 'out'; } if ($this->stock_qty < $this->safety_stock) { return 'low'; } return 'normal'; } /** * 재고 정보 업데이트 (LOT 기반) */ public function refreshFromLots(): void { $lots = $this->lots()->where('status', '!=', 'used')->get(); $this->lot_count = $lots->count(); $this->stock_qty = $lots->sum('qty'); $this->reserved_qty = $lots->sum('reserved_qty'); $this->available_qty = $lots->sum('available_qty'); $oldestLot = $lots->sortBy('receipt_date')->first(); $this->oldest_lot_date = $oldestLot?->receipt_date; $this->last_receipt_date = $lots->max('receipt_date'); $this->status = $this->calculateStatus(); $this->save(); } }