рубли) 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(); } // ВЫЧИСЛЯЕМЫЕ ПОЛЯ (с учетом текущего года!) public function getQuantityWithoutDocsAttribute(): int { // Убираем фильтр по положительному остатку, чтобы учитывать недостачу return $this->orders() ->where('year', year()) // КРИТИЧНО! Учитываем текущий год из сессии ->where('status', SparePartOrder::STATUS_IN_STOCK) ->where('with_documents', false) ->sum('remaining_quantity') ?? 0; } public function getQuantityWithDocsAttribute(): int { // Убираем фильтр по положительному остатку, чтобы учитывать недостачу return $this->orders() ->where('year', year()) // КРИТИЧНО! Учитываем текущий год из сессии ->where('status', SparePartOrder::STATUS_IN_STOCK) ->where('with_documents', true) ->sum('remaining_quantity') ?? 0; } public function getTotalQuantityAttribute(): int { return $this->quantity_without_docs + $this->quantity_with_docs; } // Методы проверки 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) ); } }