рубли) protected function purchasePrice(): Attribute { return Attribute::make( get: fn($value) => $value ? $value / 100 : null, set: fn($value) => $value ? round($value * 100) : null, ); } protected function customerPrice(): Attribute { return Attribute::make( get: fn($value) => $value ? $value / 100 : null, set: fn($value) => $value ? round($value * 100) : null, ); } protected function expertisePrice(): Attribute { return Attribute::make( get: fn($value) => $value ? $value / 100 : null, set: fn($value) => $value ? round($value * 100) : null, ); } // Текстовое представление цен protected function purchasePriceTxt(): Attribute { return Attribute::make( get: fn() => isset($this->attributes['purchase_price']) && $this->attributes['purchase_price'] !== null ? Price::format($this->attributes['purchase_price'] / 100) : '-', ); } protected function customerPriceTxt(): Attribute { return Attribute::make( get: fn() => isset($this->attributes['customer_price']) && $this->attributes['customer_price'] !== null ? Price::format($this->attributes['customer_price'] / 100) : '-', ); } protected function expertisePriceTxt(): Attribute { return Attribute::make( get: fn() => isset($this->attributes['expertise_price']) && $this->attributes['expertise_price'] !== null ? Price::format($this->attributes['expertise_price'] / 100) : '-', ); } // Атрибут для картинки public function image(): Attribute { $path = ''; if (file_exists(public_path() . '/images/spare_parts/' . $this->article . '.jpg')) { $path = url('/images/spare_parts/' . $this->article . '.jpg'); } return Attribute::make(get: fn() => $path); } // Отношения public function product(): BelongsTo { return $this->belongsTo(Product::class); } public function orders(): HasMany { return $this->hasMany(SparePartOrder::class); } public function reclamations(): BelongsToMany { return $this->belongsToMany(Reclamation::class, 'reclamation_spare_part') ->withPivot('quantity', 'with_documents') ->withTimestamps(); } // ВЫЧИСЛЯЕМЫЕ ПОЛЯ (с учетом текущего года!) // Примечание: YearScope автоматически применяется к SparePartOrder, // поэтому явный фильтр по году НЕ нужен public function getQuantityWithoutDocsAttribute(): int { return (int) ($this->orders() ->where('status', SparePartOrder::STATUS_IN_STOCK) ->where('with_documents', false) ->sum('remaining_quantity') ?? 0); } public function getQuantityWithDocsAttribute(): int { return (int) ($this->orders() ->where('status', SparePartOrder::STATUS_IN_STOCK) ->where('with_documents', true) ->sum('remaining_quantity') ?? 0); } public function getTotalQuantityAttribute(): int { // Общее количество — сумма всех остатков на складе за текущий год, // независимо от наличия документов return (int) ($this->orders() ->where('status', SparePartOrder::STATUS_IN_STOCK) ->sum('remaining_quantity') ?? 0); } // Методы проверки public function hasCriticalShortage(): bool { return $this->quantity_without_docs < 0 || $this->quantity_with_docs < 0; } public function isBelowMinStock(): bool { return $this->total_quantity < $this->min_stock && $this->total_quantity >= 0; } // Расшифровки из справочника protected function tsnNumberDescription(): Attribute { return Attribute::make( get: fn() => PricingCode::getTsnDescription($this->tsn_number) ); } protected function pricingCodeDescription(): Attribute { return Attribute::make( get: fn() => PricingCode::getPricingCodeDescription($this->pricing_code) ); } }