SparePartInventoryService.php 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. <?php
  2. namespace App\Services;
  3. use App\Models\SparePart;
  4. use App\Models\SparePartOrder;
  5. use Illuminate\Support\Collection;
  6. class SparePartInventoryService
  7. {
  8. /**
  9. * Автоматическое списание запчасти для рекламации
  10. */
  11. public function deductForReclamation(
  12. string $article,
  13. int $quantity,
  14. bool $withDocuments,
  15. int $reclamationId
  16. ): bool {
  17. $sparePart = SparePart::where('article', $article)->first();
  18. if (!$sparePart) {
  19. return false;
  20. }
  21. // Ищем заказ для списания (FIFO - первый созданный)
  22. $order = SparePartOrder::where('spare_part_id', $sparePart->id)
  23. ->where('status', SparePartOrder::STATUS_IN_STOCK)
  24. ->where('with_documents', $withDocuments)
  25. ->where('remaining_quantity', '>=', $quantity)
  26. ->orderBy('created_at', 'asc')
  27. ->first();
  28. if ($order) {
  29. // Списываем
  30. $note = "Автоматическое списание для рекламации: #{$reclamationId}";
  31. return $order->shipQuantity($quantity, $note, $reclamationId, null);
  32. } else {
  33. // Создаём виртуальный заказ с недостачей
  34. // Статус IN_STOCK чтобы учитывалось в вычисляемых полях!
  35. SparePartOrder::create([
  36. 'spare_part_id' => $sparePart->id,
  37. 'source_text' => "Автоспискание для рекламации #{$reclamationId}",
  38. 'status' => SparePartOrder::STATUS_IN_STOCK,
  39. 'ordered_quantity' => 0,
  40. 'remaining_quantity' => -$quantity,
  41. 'with_documents' => $withDocuments,
  42. 'note' => "Недостача, созданная автоматически",
  43. 'user_id' => auth()->id() ?? 1,
  44. ]);
  45. return false;
  46. }
  47. }
  48. /**
  49. * Получить список запчастей с критическим недостатком (отрицательное количество)
  50. */
  51. public function getCriticalShortages(): Collection
  52. {
  53. return SparePart::all()->filter(function ($sparePart) {
  54. return $sparePart->hasCriticalShortage();
  55. });
  56. }
  57. /**
  58. * Получить список запчастей ниже минимального остатка
  59. */
  60. public function getBelowMinStock(): Collection
  61. {
  62. return SparePart::all()->filter(function ($sparePart) {
  63. return $sparePart->isBelowMinStock();
  64. });
  65. }
  66. /**
  67. * Рассчитать сколько нужно заказать для компенсации недостачи
  68. */
  69. public function calculateOrderQuantity(SparePart $sparePart, bool $withDocuments): int
  70. {
  71. $quantity = $withDocuments ? $sparePart->quantity_with_docs : $sparePart->quantity_without_docs;
  72. if ($quantity >= 0) {
  73. return 0;
  74. }
  75. return abs($quantity);
  76. }
  77. }