'Поступление', self::TYPE_RESERVE => 'Резервирование', self::TYPE_ISSUE => 'Списание', self::TYPE_RESERVE_CANCEL => 'Отмена резерва', self::TYPE_CORRECTION => 'Коррекция', self::TYPE_CORRECTION_PLUS => 'Коррекция (+)', self::TYPE_CORRECTION_MINUS => 'Коррекция (-)', ]; // Типы источников const SOURCE_ORDER = 'order'; const SOURCE_RECLAMATION = 'reclamation'; const SOURCE_MANUAL = 'manual'; const SOURCE_SHORTAGE_FULFILLMENT = 'shortage_fulfillment'; const SOURCE_INVENTORY = 'inventory'; protected $fillable = [ 'spare_part_order_id', 'spare_part_id', 'qty', 'movement_type', 'source_type', 'source_id', 'with_documents', 'user_id', 'note', ]; protected $casts = [ 'qty' => 'integer', 'with_documents' => 'boolean', 'source_id' => 'integer', ]; // Отношения public function sparePartOrder(): BelongsTo { return $this->belongsTo(SparePartOrder::class); } public function sparePart(): BelongsTo { return $this->belongsTo(SparePart::class); } public function user(): BelongsTo { return $this->belongsTo(User::class); } // Аксессоры public function getTypeNameAttribute(): string { return self::TYPE_NAMES[$this->movement_type] ?? $this->movement_type; } /** * Получить источник движения (полиморфная связь) */ public function getSourceAttribute(): ?Model { if (!$this->source_type || !$this->source_id) { return null; } return match ($this->source_type) { self::SOURCE_RECLAMATION => Reclamation::find($this->source_id), self::SOURCE_SHORTAGE_FULFILLMENT => Shortage::find($this->source_id), default => null, }; } // Scopes public function scopeOfType($query, string $type) { return $query->where('movement_type', $type); } public function scopeForSparePart($query, int $sparePartId) { return $query->where('spare_part_id', $sparePartId); } public function scopeWithDocuments($query, bool $withDocs = true) { return $query->where('with_documents', $withDocs); } /** * Движения, которые увеличивают доступный остаток */ public function scopeIncreasing($query) { return $query->whereIn('movement_type', [ self::TYPE_RECEIPT, self::TYPE_RESERVE_CANCEL, self::TYPE_CORRECTION_PLUS, ]); } /** * Движения, которые уменьшают доступный остаток */ public function scopeDecreasing($query) { return $query->whereIn('movement_type', [ self::TYPE_RESERVE, self::TYPE_ISSUE, self::TYPE_CORRECTION_MINUS, ]); } /** * Проверка типа коррекции */ public function isCorrection(): bool { return in_array($this->movement_type, [ self::TYPE_CORRECTION, self::TYPE_CORRECTION_PLUS, self::TYPE_CORRECTION_MINUS, ]); } }