StockController.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Http\Controllers;
  4. use App\Http\Requests\ReserveStockRequest;
  5. use App\Models\CommonCatalogItem;
  6. use App\Models\StockOrder;
  7. use App\Models\StockReservation;
  8. use App\Models\User;
  9. use App\Services\StockInventoryService;
  10. use DomainException;
  11. use Illuminate\Contracts\View\View;
  12. use Illuminate\Database\Eloquent\Builder;
  13. use Illuminate\Http\RedirectResponse;
  14. use Illuminate\Http\Request;
  15. class StockController extends Controller
  16. {
  17. public function __construct(private readonly StockInventoryService $inventoryService) {}
  18. public function index(Request $request): View
  19. {
  20. $search = trim((string) $request->query('search', ''));
  21. $kind = trim((string) $request->query('kind', ''));
  22. $status = trim((string) $request->query('status', ''));
  23. $perPage = $this->perPage($request);
  24. $items = CommonCatalogItem::query()
  25. ->whereHas('stockOrders')
  26. ->with(['imageFile', 'latestStockOrder'])
  27. ->withSum([
  28. 'stockOrders as physical_quantity' => fn (Builder $query) => $query->where('status', StockOrder::STATUS_IN_STOCK),
  29. ], 'available_quantity')
  30. ->withSum([
  31. 'stockReservations as reserved_quantity' => fn (Builder $query) => $query->active(),
  32. ], 'quantity')
  33. ->withSum([
  34. 'stockOrders as ordered_stock_quantity' => fn (Builder $query) => $query->where('status', StockOrder::STATUS_ORDERED),
  35. ], 'ordered_quantity')
  36. ->when($search !== '', function (Builder $query) use ($search): void {
  37. $query->where(function (Builder $query) use ($search): void {
  38. $query->where('article', 'like', "%{$search}%")
  39. ->orWhere('calculator_name', 'like', "%{$search}%")
  40. ->orWhere('kind', 'like', "%{$search}%");
  41. });
  42. })
  43. ->when($kind !== '', fn (Builder $query) => $query->where('kind', $kind))
  44. ->when(array_key_exists($status, StockOrder::STATUS_NAMES), fn (Builder $query) => $query
  45. ->whereHas('stockOrders', fn (Builder $query) => $query->where('status', $status)))
  46. ->orderBy('article')
  47. ->paginate($perPage)
  48. ->withQueryString();
  49. return view('stock.index', [
  50. 'active' => 'stock',
  51. 'items' => $items,
  52. 'search' => $search,
  53. 'kind' => $kind,
  54. 'status' => $status,
  55. 'kinds' => CommonCatalogItem::query()
  56. ->whereHas('stockOrders')
  57. ->whereNotNull('kind')
  58. ->where('kind', '<>', '')
  59. ->distinct()
  60. ->orderBy('kind')
  61. ->pluck('kind'),
  62. 'statuses' => StockOrder::STATUS_NAMES,
  63. 'per_page' => $perPage,
  64. ]);
  65. }
  66. public function show(CommonCatalogItem $item): View
  67. {
  68. $item->load([
  69. 'imageFile',
  70. 'stockOrders' => fn ($query) => $query
  71. ->withSum(['reservations as active_reservations_sum_quantity' => fn ($query) => $query->active()], 'quantity')
  72. ->orderByDesc('created_at'),
  73. 'stockReservations' => fn ($query) => $query
  74. ->with(['manager', 'order'])
  75. ->orderByDesc('created_at'),
  76. 'stockMovements' => fn ($query) => $query
  77. ->with(['user', 'order'])
  78. ->orderByDesc('created_at'),
  79. ]);
  80. return view('stock.show', [
  81. 'active' => 'stock',
  82. 'item' => $item,
  83. 'summary' => $this->inventoryService->summary($item),
  84. 'managers' => User::query()->orderBy('name')->get(),
  85. ]);
  86. }
  87. public function reserve(ReserveStockRequest $request, CommonCatalogItem $item): RedirectResponse
  88. {
  89. /** @var User $actor */
  90. $actor = $request->user();
  91. $manager = User::query()->findOrFail((int) $request->validated('manager_id'));
  92. try {
  93. $this->inventoryService->reserve(
  94. $item,
  95. $manager,
  96. (int) $request->validated('quantity'),
  97. $request->validated('note'),
  98. $actor,
  99. );
  100. return back()->with('success', 'Позиция забронирована.');
  101. } catch (DomainException $exception) {
  102. return back()->withErrors(['quantity' => $exception->getMessage()])->withInput();
  103. }
  104. }
  105. public function issue(Request $request, StockReservation $reservation): RedirectResponse
  106. {
  107. /** @var User $actor */
  108. $actor = $request->user();
  109. try {
  110. $this->inventoryService->issue($reservation, $actor, $request->string('note')->toString());
  111. return back()->with('success', 'Бронь списана.');
  112. } catch (DomainException $exception) {
  113. return back()->with('error', $exception->getMessage());
  114. }
  115. }
  116. public function cancel(Request $request, StockReservation $reservation): RedirectResponse
  117. {
  118. /** @var User $actor */
  119. $actor = $request->user();
  120. try {
  121. $this->inventoryService->cancel($reservation, $actor, $request->string('note')->toString());
  122. return back()->with('success', 'Бронь отменена.');
  123. } catch (DomainException $exception) {
  124. return back()->with('error', $exception->getMessage());
  125. }
  126. }
  127. private function perPage(Request $request): int
  128. {
  129. $allowed = [50, 100, 200, 500, 2000];
  130. $perPage = (int) $request->query('per_page', session('per_page', 50));
  131. $perPage = in_array($perPage, $allowed, true) ? $perPage : 50;
  132. session(['per_page' => $perPage]);
  133. return $perPage;
  134. }
  135. }