| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- <?php
- namespace App\Services;
- use App\Models\Reservation;
- use App\Models\Shortage;
- use App\Models\SparePart;
- use App\Models\SparePartOrder;
- use Illuminate\Support\Collection;
- /**
- * Сервис контроля наличия запчастей.
- *
- * Предоставляет методы для:
- * - Получения критических дефицитов
- * - Получения запчастей ниже минимального остатка
- * - Расчёта рекомендованного количества для заказа
- *
- * ВАЖНО: Для резервирования и списания используйте
- * SparePartReservationService и SparePartIssueService.
- */
- class SparePartInventoryService
- {
- public function __construct(
- private readonly SparePartReservationService $reservationService,
- private readonly ShortageService $shortageService
- ) {}
- /**
- * Зарезервировать запчасть для рекламации
- *
- * @deprecated Используйте SparePartReservationService::reserve()
- */
- public function reserveForReclamation(
- string $article,
- int $quantity,
- bool $withDocuments,
- int $reclamationId
- ): ReservationResult {
- $sparePart = SparePart::where('article', $article)->first();
- if (!$sparePart) {
- return new ReservationResult(
- reserved: 0,
- missing: $quantity,
- reservations: collect(),
- shortage: null
- );
- }
- return $this->reservationService->reserve(
- $sparePart->id,
- $quantity,
- $withDocuments,
- $reclamationId
- );
- }
- /**
- * @deprecated Используйте SparePartReservationService::reserve()
- */
- public function deductForReclamation(
- string $article,
- int $quantity,
- bool $withDocuments,
- int $reclamationId
- ): bool {
- $result = $this->reserveForReclamation($article, $quantity, $withDocuments, $reclamationId);
- return $result->reserved > 0;
- }
- /**
- * Получить список запчастей с критическим дефицитом (открытые дефициты)
- */
- public function getCriticalShortages(): Collection
- {
- return $this->shortageService->getCriticalShortages();
- }
- /**
- * Получить список запчастей ниже минимального остатка
- */
- public function getBelowMinStock(): Collection
- {
- return SparePart::all()->filter(function ($sparePart) {
- return $sparePart->isBelowMinStock();
- });
- }
- /**
- * Рассчитать сколько нужно заказать для покрытия дефицита
- */
- public function calculateOrderQuantity(SparePart $sparePart, bool $withDocuments): int
- {
- return $this->shortageService->calculateOrderQuantity($sparePart->id, $withDocuments);
- }
- /**
- * Рассчитать сколько нужно заказать для достижения минимального остатка
- */
- public function calculateMinStockOrderQuantity(SparePart $sparePart): int
- {
- $freeStock = $sparePart->total_free_stock;
- $minStock = $sparePart->min_stock;
- if ($freeStock >= $minStock) {
- return 0;
- }
- return $minStock - $freeStock;
- }
- /**
- * Получить полную информацию по остаткам запчасти
- */
- public function getStockInfo(SparePart $sparePart): array
- {
- return [
- 'spare_part_id' => $sparePart->id,
- 'article' => $sparePart->article,
- // Физические остатки
- 'physical_without_docs' => $sparePart->physical_stock_without_docs,
- 'physical_with_docs' => $sparePart->physical_stock_with_docs,
- 'physical_total' => $sparePart->total_physical_stock,
- // Зарезервировано
- 'reserved_without_docs' => $sparePart->reserved_without_docs,
- 'reserved_with_docs' => $sparePart->reserved_with_docs,
- 'reserved_total' => $sparePart->total_reserved,
- // Свободно
- 'free_without_docs' => $sparePart->free_stock_without_docs,
- 'free_with_docs' => $sparePart->free_stock_with_docs,
- 'free_total' => $sparePart->total_free_stock,
- // Дефициты
- 'has_shortages' => $sparePart->hasOpenShortages(),
- 'shortage_qty' => $sparePart->open_shortages_qty,
- // Минимальный остаток
- 'min_stock' => $sparePart->min_stock,
- 'below_min_stock' => $sparePart->isBelowMinStock(),
- // Рекомендации
- 'recommended_order_without_docs' => $this->calculateOrderQuantity($sparePart, false),
- 'recommended_order_with_docs' => $this->calculateOrderQuantity($sparePart, true),
- 'recommended_for_min_stock' => $this->calculateMinStockOrderQuantity($sparePart),
- ];
- }
- /**
- * Получить сводку по всем запчастям
- */
- public function getInventorySummary(): array
- {
- $spareParts = SparePart::all();
- $criticalCount = 0;
- $belowMinCount = 0;
- $totalPhysical = 0;
- $totalReserved = 0;
- foreach ($spareParts as $sparePart) {
- if ($sparePart->hasOpenShortages()) {
- $criticalCount++;
- }
- if ($sparePart->isBelowMinStock()) {
- $belowMinCount++;
- }
- $totalPhysical += $sparePart->total_physical_stock;
- $totalReserved += $sparePart->total_reserved;
- }
- return [
- 'total_spare_parts' => $spareParts->count(),
- 'critical_shortages_count' => $criticalCount,
- 'below_min_stock_count' => $belowMinCount,
- 'total_physical_stock' => $totalPhysical,
- 'total_reserved' => $totalReserved,
- 'total_free' => $totalPhysical - $totalReserved,
- ];
- }
- /**
- * Получить резервы для рекламации
- */
- public function getReservationsForReclamation(int $reclamationId): Collection
- {
- return $this->reservationService->getReservationsForReclamation($reclamationId);
- }
- /**
- * Получить дефициты для рекламации
- */
- public function getShortagesForReclamation(int $reclamationId): Collection
- {
- return Shortage::query()
- ->where('reclamation_id', $reclamationId)
- ->with('sparePart')
- ->get();
- }
- }
|