Ver Fonte

Replace previous_url with session nav context

Alexander Musikhin há 1 dia atrás
pai
commit
83cd2d0975
52 ficheiros alterados com 1350 adições e 245 exclusões
  1. 8 1
      app/Http/Controllers/Admin/AdminAreaController.php
  2. 8 1
      app/Http/Controllers/Admin/AdminDistrictController.php
  3. 21 2
      app/Http/Controllers/ContractController.php
  4. 17 28
      app/Http/Controllers/Controller.php
  5. 8 2
      app/Http/Controllers/ImportController.php
  6. 13 3
      app/Http/Controllers/MafOrderController.php
  7. 21 7
      app/Http/Controllers/OrderController.php
  8. 32 16
      app/Http/Controllers/ProductController.php
  9. 24 26
      app/Http/Controllers/ProductSKUController.php
  10. 40 28
      app/Http/Controllers/ReclamationController.php
  11. 5 5
      app/Http/Controllers/ReportController.php
  12. 12 1
      app/Http/Controllers/ResponsibleController.php
  13. 30 14
      app/Http/Controllers/SparePartController.php
  14. 35 19
      app/Http/Controllers/SparePartOrderController.php
  15. 21 2
      app/Http/Controllers/UserController.php
  16. 202 0
      app/Services/NavigationContextService.php
  17. 6 5
      resources/views/admin/areas/edit.blade.php
  18. 1 0
      resources/views/admin/areas/index.blade.php
  19. 6 5
      resources/views/admin/districts/edit.blade.php
  20. 1 0
      resources/views/admin/districts/index.blade.php
  21. 4 4
      resources/views/catalog/edit.blade.php
  22. 4 3
      resources/views/contracts/edit.blade.php
  23. 2 1
      resources/views/contracts/index.blade.php
  24. 1 1
      resources/views/import/show.blade.php
  25. 4 4
      resources/views/maf_orders/edit.blade.php
  26. 2 1
      resources/views/orders/edit.blade.php
  27. 5 5
      resources/views/orders/show.blade.php
  28. 1 1
      resources/views/partials/submit.blade.php
  29. 2 2
      resources/views/partials/table.blade.php
  30. 4 4
      resources/views/products_sku/edit.blade.php
  31. 20 11
      resources/views/reclamations/edit.blade.php
  32. 4 3
      resources/views/responsibles/edit.blade.php
  33. 3 1
      resources/views/responsibles/index.blade.php
  34. 8 8
      resources/views/spare_part_orders/edit.blade.php
  35. 9 9
      resources/views/spare_parts/edit.blade.php
  36. 5 4
      resources/views/users/edit.blade.php
  37. 3 1
      resources/views/users/index.blade.php
  38. 22 0
      tests/Feature/AdminAreaControllerTest.php
  39. 22 0
      tests/Feature/AdminDistrictControllerTest.php
  40. 22 0
      tests/Feature/ContractControllerTest.php
  41. 31 0
      tests/Feature/ImportControllerTest.php
  42. 41 1
      tests/Feature/MafOrderControllerTest.php
  43. 67 0
      tests/Feature/OrderControllerTest.php
  44. 60 0
      tests/Feature/ProductControllerTest.php
  45. 67 0
      tests/Feature/ProductSKUControllerTest.php
  46. 94 1
      tests/Feature/ReclamationControllerTest.php
  47. 1 3
      tests/Feature/ReportControllerTest.php
  48. 26 0
      tests/Feature/ResponsibleControllerTest.php
  49. 80 0
      tests/Feature/SparePartControllerTest.php
  50. 139 12
      tests/Feature/SparePartOrderControllerTest.php
  51. 22 0
      tests/Feature/UserControllerTest.php
  52. 64 0
      tests/Unit/Services/NavigationContextServiceTest.php

+ 8 - 1
app/Http/Controllers/Admin/AdminAreaController.php

@@ -25,6 +25,8 @@ class AdminAreaController extends Controller
 
     public function index(Request $request): View
     {
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $allowedSortFields = ['id', 'name', 'district_name'];
         $sortBy = in_array($request->get('sortBy'), $allowedSortFields, true) ? $request->get('sortBy') : 'name';
         $orderBy = $request->get('order') === 'desc' ? 'desc' : 'asc';
@@ -66,11 +68,14 @@ class AdminAreaController extends Controller
             'areas' => $areas,
             'districts' => $districts,
             'selectedDistrict' => $request->district_id,
+            'nav' => $nav,
         ]);
     }
 
-    public function show(int $areaId): View
+    public function show(Request $request, int $areaId): View
     {
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $area = Area::withTrashed()->findOrFail($areaId);
         $districts = District::orderBy('shortname')->pluck('name', 'id')->toArray();
 
@@ -78,6 +83,8 @@ class AdminAreaController extends Controller
             'active' => 'admin_areas',
             'area' => $area,
             'districts' => $districts,
+            'nav' => $nav,
+            'back_url' => $this->navigationBackUrl($request, $nav, route('admin.area.index')),
         ]);
     }
 

+ 8 - 1
app/Http/Controllers/Admin/AdminDistrictController.php

@@ -25,6 +25,8 @@ class AdminDistrictController extends Controller
 
     public function index(Request $request): View
     {
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $allowedSortFields = ['id', 'shortname', 'name', 'areas_count'];
         $sortBy = in_array($request->get('sortBy'), $allowedSortFields, true) ? $request->get('sortBy') : 'id';
         $orderBy = $request->get('order') === 'desc' ? 'desc' : 'asc';
@@ -51,16 +53,21 @@ class AdminDistrictController extends Controller
             'orderBy' => $orderBy,
             'searchFields' => $this->searchFields,
             'districts' => $districts,
+            'nav' => $nav,
         ]);
     }
 
-    public function show(int $districtId): View
+    public function show(Request $request, int $districtId): View
     {
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $district = District::withTrashed()->findOrFail($districtId);
 
         return view('admin.districts.edit', [
             'active' => 'admin_districts',
             'district' => $district,
+            'nav' => $nav,
+            'back_url' => $this->navigationBackUrl($request, $nav, route('admin.district.index')),
         ]);
     }
 

+ 21 - 2
app/Http/Controllers/ContractController.php

@@ -27,6 +27,8 @@ class ContractController extends Controller
     public function index(Request $request)
     {
         session(['gp_contracts' => $request->query()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
 
         $model = new Contract();
         // fill filters
@@ -41,18 +43,35 @@ class ContractController extends Controller
 
         $this->applyStableSorting($q);
         $this->data['contracts'] = $q->paginate($this->data['per_page'])->withQueryString();
+        $this->data['nav'] = $nav;
 
         return view('contracts.index', $this->data);
     }
 
-    public function show(Contract $contract)
+    public function show(Request $request, Contract $contract)
     {
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
+            $request,
+            $nav,
+            route('contract.index', session('gp_contracts', []))
+        );
         $this->data['contract'] = $contract;
         return view('contracts.edit', $this->data);
     }
 
-    public function create()
+    public function create(Request $request)
     {
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
+            $request,
+            $nav,
+            route('contract.index', session('gp_contracts', []))
+        );
         $this->data['contract'] = null;
         return view('contracts.edit', $this->data);
     }

+ 17 - 28
app/Http/Controllers/Controller.php

@@ -3,6 +3,7 @@
 namespace App\Http\Controllers;
 
 use App\Helpers\DateHelper;
+use App\Services\NavigationContextService;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
@@ -22,41 +23,29 @@ class Controller extends BaseController
         'dates'     => [],
     ];
 
-    protected function resolvePreviousUrl(Request $request, string $sessionKey, ?string $fallback = null): ?string
+    protected function resolveNavToken(Request $request): string
     {
-        $previousUrl = $request->get('previous_url');
-        if (!empty($previousUrl)) {
-            session([$sessionKey => $previousUrl]);
-            return $previousUrl;
-        }
-
-        $previousUrl = session($sessionKey);
-        if (!empty($previousUrl)) {
-            return $previousUrl;
-        }
-
-        if (!empty($fallback)) {
-            session([$sessionKey => $fallback]);
-            return $fallback;
-        }
+        return app(NavigationContextService::class)->getOrCreateToken($request);
+    }
 
-        return null;
+    protected function rememberNavigation(Request $request, string $token): void
+    {
+        app(NavigationContextService::class)->rememberCurrentPage($request, $token);
     }
 
-    protected function previousUrlForRedirect(Request $request, string $sessionKey, ?string $fallback = null): ?string
+    protected function navigationBackUrl(Request $request, string $token, ?string $fallback = null): ?string
     {
-        $previousUrl = $request->get('previous_url');
-        if (!empty($previousUrl)) {
-            session([$sessionKey => $previousUrl]);
-            return $previousUrl;
-        }
+        return app(NavigationContextService::class)->backUrl($request, $token, $fallback);
+    }
 
-        $previousUrl = session($sessionKey);
-        if (!empty($previousUrl)) {
-            return $previousUrl;
-        }
+    protected function navigationParentUrl(string $token, ?string $fallback = null): ?string
+    {
+        return app(NavigationContextService::class)->parentUrl($token, $fallback);
+    }
 
-        return $fallback;
+    protected function withNav(array $params, string $token): array
+    {
+        return app(NavigationContextService::class)->routeParams($params, $token);
     }
 
     /**

+ 8 - 2
app/Http/Controllers/ImportController.php

@@ -35,6 +35,8 @@ class ImportController extends Controller
     public function index(Request $request)
     {
         session(['gp_import' => $request->all()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $model = new Import;
         // fill filters
         $this->data['ranges'] = [];
@@ -50,6 +52,7 @@ class ImportController extends Controller
 
         $this->applyStableSorting($q);
         $this->data['imports'] = $q->paginate($this->data['per_page'])->withQueryString();
+        $this->data['nav'] = $nav;
 
         return view('import.index', $this->data);
     }
@@ -84,9 +87,12 @@ class ImportController extends Controller
     public function show(Request $request, Import $import)
     {
         $this->data['import'] = $import;
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_import',
+            $nav,
             route('import.index', session('gp_import'))
         );
         return view('import.show', $this->data);

+ 13 - 3
app/Http/Controllers/MafOrderController.php

@@ -39,6 +39,8 @@ class MafOrderController extends Controller
     {
         $model = new MafOrdersView;
         session(['gp_maf_order' => $request->all()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $this->createDateFilters($model, 'created_at');
         $this->createFilters($model, 'user_name');
         $this->createRangeFilters($model, 'in_stock');
@@ -60,6 +62,7 @@ class MafOrderController extends Controller
             ->distinct()
             ->pluck('order_number')
             ->values();
+        $this->data['nav'] = $nav;
 
         return view('maf_orders.index', $this->data);
     }
@@ -73,9 +76,12 @@ class MafOrderController extends Controller
     public function show(Request $request, int $maf_order)
     {
         $this->data['maf_order'] = MafOrder::query()->withoutGlobalScope(\App\Models\Scopes\YearScope::class)->find($maf_order);
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_maf_order',
+            $nav,
             route('maf_order.index', session('gp_maf_order'))
         );
         return view('maf_orders.edit', $this->data);
@@ -97,7 +103,11 @@ class MafOrderController extends Controller
     public function setInStock(MafOrder $maf_order)
     {
         $maf_order->update(['in_stock' => $maf_order->quantity, 'status' => 'на складе']);
-        return redirect()->route('maf_order.show', $maf_order);
+
+        $request = request();
+        $nav = $this->resolveNavToken($request);
+
+        return redirect()->route('maf_order.show', $this->withNav(['maf_order' => $maf_order], $nav));
     }
 
     public function setOrderInStock(Request $request)

+ 21 - 7
app/Http/Controllers/OrderController.php

@@ -92,6 +92,8 @@ class OrderController extends Controller
     public function index(Request $request)
     {
         session(['gp_orders' => $request->all()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $model = new OrderView;
         // fill filters
         $this->createFilters($model, 'user_name', 'district_name', 'area_name', 'object_type_name', 'brigadier_name', 'order_status_name', 'ready_to_mount');
@@ -125,6 +127,7 @@ class OrderController extends Controller
         }
 
         $this->data['statuses'] = OrderStatus::query()->get()->pluck('name', 'id');
+        $this->data['nav'] = $nav;
 
         return view('orders.index', $this->data);
     }
@@ -134,9 +137,12 @@ class OrderController extends Controller
      */
     public function create(Request $request)
     {
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_orders',
+            $nav,
             route('order.index', session('gp_orders'))
         );
         return view('orders.edit', $this->data);
@@ -263,7 +269,9 @@ class OrderController extends Controller
         $order->refresh();
         $order->autoChangeStatus();
 
-        return redirect()->route('order.show', ['order' => $order, 'previous_url' => $request->get('previous_url')]);
+        $nav = $this->resolveNavToken($request);
+
+        return redirect()->route('order.show', $this->withNav(['order' => $order], $nav));
     }
 
     private function validateMountStatusChange(Order $order, mixed $newStatusId): array
@@ -321,9 +329,12 @@ class OrderController extends Controller
             }
         }
 
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_orders',
+            $nav,
             route('order.index', session('gp_orders'))
         );
         $orderModel = $this->data['order'];
@@ -355,9 +366,12 @@ class OrderController extends Controller
     public function edit(Request $request, Order $order)
     {
         $this->data['order'] = $order;
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_orders',
+            $nav,
             route('order.index', session('gp_orders'))
         );
         return view('orders.edit', $this->data);

+ 32 - 16
app/Http/Controllers/ProductController.php

@@ -60,6 +60,8 @@ class ProductController extends Controller
     public function index(Request $request)
     {
         session(['gp_products' => $request->query()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $model = new Product;
         // fill filters
         $this->createFilters($model, 'type_tz', 'type', 'certificate_id');
@@ -76,14 +78,18 @@ class ProductController extends Controller
         $this->applyStableSorting($q);
 
         $this->data['products'] = $q->paginate($this->data['per_page'])->withQueryString();
+        $this->data['nav'] = $nav;
         return view('catalog.index', $this->data);
     }
 
     public function show(Request $request, int $product)
     {
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_products',
+            $nav,
             route('catalog.index', session('gp_products'))
         );
         $this->data['product'] = Product::query()->withoutGlobalScope(\App\Models\Scopes\YearScope::class)->find($product);
@@ -92,9 +98,12 @@ class ProductController extends Controller
 
     public function create(Request $request)
     {
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_products',
+            $nav,
             route('catalog.index', session('gp_products'))
         );
         $this->data['product'] = null;
@@ -104,23 +113,23 @@ class ProductController extends Controller
     public function store(StoreProductRequest $request)
     {
         Product::create($request->validated());
-        $previous_url = $this->previousUrlForRedirect(
-            $request,
-            'previous_url_products',
+        $nav = $this->resolveNavToken($request);
+        $backUrl = $this->navigationParentUrl(
+            $nav,
             route('catalog.index', session('gp_products'))
         );
-        return redirect()->to($previous_url);
+        return redirect()->to($backUrl);
     }
 
     public function update(StoreProductRequest $request, Product $product)
     {
         $product->update($request->validated());
-        $previous_url = $this->previousUrlForRedirect(
-            $request,
-            'previous_url_products',
+        $nav = $this->resolveNavToken($request);
+        $backUrl = $this->navigationParentUrl(
+            $nav,
             route('catalog.index', session('gp_products'))
         );
-        return redirect()->to($previous_url);
+        return redirect()->to($backUrl);
     }
 
     public function delete(Product $product)
@@ -186,11 +195,11 @@ class ProductController extends Controller
             $product->update(['certificate_id' => $f->id]);
         } catch (Throwable $e) {
             report($e);
-            return redirect()->route('catalog.show', ['product' => $product, 'previous_url' => $request->get('previous_url')])
+            return $this->redirectToCatalogShow($request, $product)
                 ->with(['error' => 'Ошибка загрузки сертификата. Проверьте имя файла и повторите попытку.']);
         }
 
-        return redirect()->route('catalog.show', ['product' => $product, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToCatalogShow($request, $product)
             ->with(['success' => 'Сертификат успешно загружен!']);
     }
 
@@ -199,7 +208,7 @@ class ProductController extends Controller
         $product->update(['certificate_id' => null]);
         Storage::disk('public')->delete($file->path);
         $file->delete();
-        return redirect()->route('catalog.show', ['product' => $product, 'previous_url' => $request->get('previous_url')]);
+        return $this->redirectToCatalogShow($request, $product);
     }
 
     public function uploadThumbnail(Request $request, Product $product): RedirectResponse
@@ -221,9 +230,16 @@ class ProductController extends Controller
         // Сохраняем новый файл
         $file->move($destinationPath, $filename);
 
-        return redirect()->route('catalog.show', ['product' => $product, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToCatalogShow($request, $product)
             ->with('success', 'Миниатюра успешно загружена');
     }
 
+    private function redirectToCatalogShow(Request $request, Product $product): RedirectResponse
+    {
+        $nav = $this->resolveNavToken($request);
+
+        return redirect()->route('catalog.show', $this->withNav(['product' => $product], $nav));
+    }
+
 
 }

+ 24 - 26
app/Http/Controllers/ProductSKUController.php

@@ -57,6 +57,8 @@ class ProductSKUController extends Controller
     public function index(Request $request)
     {
         session(['gp_product_sku' => $request->all()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $model = new MafView;
 
         $fil = array_keys($this->data['header']);
@@ -75,14 +77,15 @@ class ProductSKUController extends Controller
 
 //        dump($q->toRawSql());
         $this->data['products_sku'] = $q->paginate($this->data['per_page'])->withQueryString();
+        $this->data['nav'] = $nav;
         return view('products_sku.index', $this->data);
     }
 
     public function update(ProductSKUStoreRequest $request, ProductSKU $product_sku)
     {
-        $url = $this->previousUrlForRedirect(
-            $request,
-            'previous_url_product_sku',
+        $nav = $this->resolveNavToken($request);
+        $url = $this->navigationParentUrl(
+            $nav,
             route('product_sku.index', session('gp_product_sku'))
         );
 
@@ -93,9 +96,12 @@ class ProductSKUController extends Controller
     public function show(Request $request, int $product_sku)
     {
         $this->data['product_sku'] = ProductSKU::query()->withoutGlobalScope(\App\Models\Scopes\YearScope::class)->find($product_sku);
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_product_sku',
+            $nav,
             route('product_sku.index', session('gp_product_sku'))
         );
         return view('products_sku.edit', $this->data);
@@ -112,35 +118,20 @@ class ProductSKUController extends Controller
             $product_sku->update(['passport_id' => $f->id]);
         } catch (Throwable $e) {
             report($e);
-            return redirect()->route('product_sku.show', [
-                'product_sku' => $product_sku,
-                'previous_url' => $this->previousUrlForRedirect(
-                    $request,
-                    'previous_url_product_sku',
-                    route('product_sku.index', session('gp_product_sku'))
-                ),
-            ])->with(['error' => 'Ошибка загрузки паспорта. Проверьте имя файла и повторите попытку.']);
+            return $this->redirectToProductSkuShow($request, $product_sku)
+                ->with(['error' => 'Ошибка загрузки паспорта. Проверьте имя файла и повторите попытку.']);
         }
 
-        return redirect()->route('product_sku.show', [
-            'product_sku' => $product_sku,
-            'previous_url' => $this->previousUrlForRedirect(
-                $request,
-                'previous_url_product_sku',
-                route('product_sku.index', session('gp_product_sku'))
-            ),
-        ])->with(['success' => 'Паспорт успешно загружен!']);
+        return $this->redirectToProductSkuShow($request, $product_sku)
+            ->with(['success' => 'Паспорт успешно загружен!']);
     }
 
-    public function deletePassport(ProductSKU $product_sku, File $file)
+    public function deletePassport(Request $request, ProductSKU $product_sku, File $file)
     {
         $product_sku->update(['passport_id' => null]);
         Storage::disk('public')->delete($file->path);
         $file->delete();
-        return redirect()->route('product_sku.show', [
-            'product_sku' => $product_sku,
-            'previous_url' => session('previous_url_product_sku'),
-        ]);
+        return $this->redirectToProductSkuShow($request, $product_sku);
     }
 
     public function exportMaf(Request $request)
@@ -168,4 +159,11 @@ class ProductSKUController extends Controller
     {
 
     }
+
+    private function redirectToProductSkuShow(Request $request, ProductSKU $productSku)
+    {
+        $nav = $this->resolveNavToken($request);
+
+        return redirect()->route('product_sku.show', $this->withNav(['product_sku' => $productSku], $nav));
+    }
 }

+ 40 - 28
app/Http/Controllers/ReclamationController.php

@@ -68,6 +68,8 @@ class ReclamationController extends Controller
     public function index(Request $request)
     {
         session(['gp_reclamations' => $request->all()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $model = new ReclamationView();
         // fill filters
         $this->createFilters($model, 'user_name', 'status_name');
@@ -89,6 +91,7 @@ class ReclamationController extends Controller
 
         $this->applyStableSorting($q);
         $this->data['reclamations'] = $q->paginate($this->data['per_page'])->withQueryString();
+        $this->data['nav'] = $nav;
 
         return view('reclamations.index', $this->data);
     }
@@ -128,6 +131,7 @@ class ReclamationController extends Controller
 
     public function create(CreateReclamationRequest $request, Order $order, NotificationService $notificationService)
     {
+        $nav = $this->resolveNavToken($request);
         $reclamation = Reclamation::query()->create([
             'order_id' => $order->id,
             'user_id' => $request->user()->id,
@@ -138,7 +142,7 @@ class ReclamationController extends Controller
         $skus = $request->validated('skus');
         $reclamation->skus()->attach($skus);
         $notificationService->notifyReclamationCreated($reclamation->fresh(['order', 'status']), auth()->user());
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => url()->previous()]);
+        return redirect()->route('reclamations.show', $this->withNav(['reclamation' => $reclamation], $nav));
     }
 
     public function show(Request $request, Reclamation $reclamation)
@@ -172,9 +176,12 @@ class ReclamationController extends Controller
         $this->data['chatResponsibleUserIds'] = $responsibleUserIds;
         $this->data['chatManagerUserId'] = $reclamation->user_id ? (int) $reclamation->user_id : null;
         $this->data['chatBrigadierUserId'] = $reclamation->brigadier_id ? (int) $reclamation->brigadier_id : null;
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_reclamations',
+            $nav,
             route('reclamations.index', session('gp_reclamations'))
         );
         return view('reclamations.edit', $this->data);
@@ -190,15 +197,13 @@ class ReclamationController extends Controller
             $notificationService->notifyReclamationStatusChanged($reclamation->fresh(['order', 'status']), auth()->user());
         }
 
-        $previousUrl = $this->previousUrlForRedirect($request, 'previous_url_reclamations');
-        if (!empty($previousUrl)) {
-            return redirect()->route('reclamations.show', [
-                'reclamation' => $reclamation,
-                'previous_url' => $previousUrl,
-            ]);
+        $nav = $this->resolveNavToken($request);
+
+        if ($request->ajax()) {
+            return response()->noContent();
         }
 
-        return redirect()->route('reclamations.show', $reclamation->id);
+        return redirect()->route('reclamations.show', $this->withNav(['reclamation' => $reclamation], $nav));
     }
 
     public function updateStatus(Request $request, Reclamation $reclamation, NotificationService $notificationService)
@@ -240,11 +245,11 @@ class ReclamationController extends Controller
             $reclamation->photos_before()->syncWithoutDetaching($f);
         } catch (Throwable $e) {
             report($e);
-            return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+            return $this->redirectToReclamationShow($request, $reclamation)
                 ->with(['error' => 'Ошибка загрузки фотографий проблемы. Проверьте имя файла и повторите попытку.']);
         }
 
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToReclamationShow($request, $reclamation)
             ->with(['success' => 'Фотографии проблемы успешно загружены!']);
     }
 
@@ -264,11 +269,11 @@ class ReclamationController extends Controller
             $reclamation->photos_after()->syncWithoutDetaching($f);
         } catch (Throwable $e) {
             report($e);
-            return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+            return $this->redirectToReclamationShow($request, $reclamation)
                 ->with(['error' => 'Ошибка загрузки фотографий после устранения. Проверьте имя файла и повторите попытку.']);
         }
 
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToReclamationShow($request, $reclamation)
             ->with(['success' => 'Фотографии после устранения успешно загружены!']);
     }
 
@@ -280,7 +285,7 @@ class ReclamationController extends Controller
         $reclamation->photos_before()->detach($file);
         Storage::disk('public')->delete($file->path);
         $file->delete();
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]);
+        return $this->redirectToReclamationShow($request, $reclamation);
     }
 
     public function deletePhotoAfter(Request $request, Reclamation $reclamation, File $file, FileService $fileService)
@@ -291,7 +296,7 @@ class ReclamationController extends Controller
         $reclamation->photos_after()->detach($file);
         Storage::disk('public')->delete($file->path);
         $file->delete();
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]);
+        return $this->redirectToReclamationShow($request, $reclamation);
     }
 
     public function uploadDocument(Request $request, Reclamation $reclamation, FileService $fileService)
@@ -313,11 +318,11 @@ class ReclamationController extends Controller
             $reclamation->documents()->syncWithoutDetaching($f);
         } catch (Throwable $e) {
             report($e);
-            return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+            return $this->redirectToReclamationShow($request, $reclamation)
                 ->with(['error' => 'Ошибка загрузки документов рекламации. Проверьте имя файла и повторите попытку.']);
         }
 
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToReclamationShow($request, $reclamation)
             ->with(['success' => 'Документы рекламации успешно загружены!']);
     }
 
@@ -329,7 +334,7 @@ class ReclamationController extends Controller
         $reclamation->documents()->detach($file);
         Storage::disk('public')->delete($file->path);
         $file->delete();
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]);
+        return $this->redirectToReclamationShow($request, $reclamation);
     }
 
     public function uploadAct(Request $request, Reclamation $reclamation, FileService $fileService)
@@ -351,11 +356,11 @@ class ReclamationController extends Controller
             $reclamation->acts()->syncWithoutDetaching($f);
         } catch (Throwable $e) {
             report($e);
-            return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+            return $this->redirectToReclamationShow($request, $reclamation)
                 ->with(['error' => 'Ошибка загрузки актов. Проверьте имя файла и повторите попытку.']);
         }
 
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToReclamationShow($request, $reclamation)
             ->with(['success' => 'Акты успешно загружены!']);
     }
 
@@ -367,7 +372,7 @@ class ReclamationController extends Controller
         $reclamation->acts()->detach($file);
         Storage::disk('public')->delete($file->path);
         $file->delete();
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]);
+        return $this->redirectToReclamationShow($request, $reclamation);
     }
 
     public function updateDetails(StoreReclamationDetailsRequest $request, Reclamation $reclamation)
@@ -467,7 +472,7 @@ class ReclamationController extends Controller
             }
         }
 
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]);
+        return $this->redirectToReclamationShow($request, $reclamation);
     }
 
     public function updateSpareParts(StoreReclamationSparePartsRequest $request, Reclamation $reclamation)
@@ -553,34 +558,34 @@ class ReclamationController extends Controller
         // Синхронизируем (заменяем все старые привязки новыми)
         $reclamation->spareParts()->sync($newSpareParts);
 
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]);
+        return $this->redirectToReclamationShow($request, $reclamation);
     }
 
     public function generateReclamationPack(Request $request, Reclamation $reclamation)
     {
         GenerateReclamationPack::dispatch($reclamation, auth()->user()->id);
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToReclamationShow($request, $reclamation)
             ->with(['success' => 'Задача генерации документов создана!']);
     }
 
     public function generateReclamationPaymentPack(Request $request, Reclamation $reclamation)
     {
         GenerateReclamationPaymentPack::dispatch($reclamation, auth()->user()->id);
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToReclamationShow($request, $reclamation)
             ->with(['success' => 'Задача генерации пакета документов на оплату создана!']);
     }
 
     public function generatePhotosBeforePack(Request $request, Reclamation $reclamation)
     {
         GenerateFilesPack::dispatch($reclamation, $reclamation->photos_before, auth()->user()->id, 'Фото проблемы');
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToReclamationShow($request, $reclamation)
             ->with(['success' => 'Задача архивации создана!']);
     }
 
     public function generatePhotosAfterPack(Request $request, Reclamation $reclamation)
     {
         GenerateFilesPack::dispatch($reclamation, $reclamation->photos_after, auth()->user()->id, 'Фото после');
-        return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')])
+        return $this->redirectToReclamationShow($request, $reclamation)
             ->with(['success' => 'Задача архивации создана!']);
     }
 
@@ -612,4 +617,11 @@ class ReclamationController extends Controller
         }
     }
 
+    private function redirectToReclamationShow(Request $request, Reclamation $reclamation)
+    {
+        $nav = $this->resolveNavToken($request);
+
+        return redirect()->route('reclamations.show', $this->withNav(['reclamation' => $reclamation], $nav));
+    }
+
 }

+ 5 - 5
app/Http/Controllers/ReportController.php

@@ -182,8 +182,10 @@ class ReportController extends Controller
             $this->getDoneSumByStatus($handedOverStatus)
         );
 
-        $districts = District::query()->get()->pluck('shortname', 'id')->toArray();
-        foreach ($districts as $districtId => $district) {
+        $districts = District::query()->get(['id', 'shortname']);
+        foreach ($districts as $districtModel) {
+            $districtId = $districtModel->id;
+            $district = $districtModel->shortname;
 
             $totalOrders = $this->getOrderCount($districtId);
             $totalMafs = $this->getMafCount($districtId);
@@ -218,9 +220,7 @@ class ReportController extends Controller
             $mountZnakMafs = $this->getMafCount($districtId, $mountStatuses, 3);
             $ostZnakOrders = $totalYardOrders - $mountYardOrders;
             $ostZnakMafs = $totalYardMafs - $mountYardMafs;
-
-
-            $this->data['byDistrict'][$district] = [
+            $this->data['byDistrict'][$districtId] = [
                 'name'  => $district,
                 'totalSum' => $this->getDistrictSum($districtId),
                 'doneSum' => $this->getDistrictSum($districtId, [$handedOverStatus]),

+ 12 - 1
app/Http/Controllers/ResponsibleController.php

@@ -51,6 +51,8 @@ class ResponsibleController extends Controller
     public function index(Request $request)
     {
         session(['gp_responsibles' => $request->query()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
 
         $model = new ResponsibleView;
         $this->createFilters($model, 'area_name');
@@ -63,6 +65,7 @@ class ResponsibleController extends Controller
 
         $this->applyStableSorting($q);
         $this->data['responsibles'] = $q->paginate($this->data['per_page'])->withQueryString();
+        $this->data['nav'] = $nav;
 
         return view('responsibles.index', $this->data);
     }
@@ -83,8 +86,16 @@ class ResponsibleController extends Controller
      * @param Responsible $responsible
      * @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application|\Illuminate\View\View
      */
-    public function show(Responsible $responsible)
+    public function show(Request $request, Responsible $responsible)
     {
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
+            $request,
+            $nav,
+            route('responsible.index', session('gp_responsibles', []))
+        );
         $this->data['responsible'] = $responsible;
         return view('responsibles.edit', $this->data);
     }

+ 30 - 14
app/Http/Controllers/SparePartController.php

@@ -51,6 +51,8 @@ class SparePartController extends Controller
     public function index(Request $request)
     {
         session(['gp_spare_parts' => $request->query()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $model = new SparePartsView();
 
         // Для админа добавляем колонку цены закупки
@@ -106,15 +108,19 @@ class SparePartController extends Controller
         $this->data['spare_parts'] = $q->paginate($this->data['per_page'])->withQueryString();
         $this->data['strings'] = $this->data['spare_parts'];
         $this->data['tab'] = 'catalog';
+        $this->data['nav'] = $nav;
 
         return view('spare_parts.index', $this->data);
     }
 
     public function show(Request $request, SparePart $sparePart)
     {
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_spare_parts',
+            $nav,
             route('spare_parts.index', session('gp_spare_parts'))
         );
         $this->data['spare_part'] = $sparePart;
@@ -146,9 +152,12 @@ class SparePartController extends Controller
 
     public function create(Request $request)
     {
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_spare_parts',
+            $nav,
             route('spare_parts.index', session('gp_spare_parts'))
         );
         $this->data['spare_part'] = null;
@@ -159,24 +168,24 @@ class SparePartController extends Controller
     {
         $sparePart = SparePart::create($request->validated());
         $this->syncPricingCodes($sparePart, $request);
-        $previous_url = $this->previousUrlForRedirect(
-            $request,
-            'previous_url_spare_parts',
+        $nav = $this->resolveNavToken($request);
+        $backUrl = $this->navigationParentUrl(
+            $nav,
             route('spare_parts.index', session('gp_spare_parts'))
         );
-        return redirect()->to($previous_url)->with(['success' => 'Запчасть успешно создана!']);
+        return redirect()->to($backUrl)->with(['success' => 'Запчасть успешно создана!']);
     }
 
     public function update(StoreSparePartRequest $request, SparePart $sparePart): RedirectResponse
     {
         $sparePart->update($request->validated());
         $this->syncPricingCodes($sparePart, $request);
-        $previous_url = $this->previousUrlForRedirect(
-            $request,
-            'previous_url_spare_parts',
+        $nav = $this->resolveNavToken($request);
+        $backUrl = $this->navigationParentUrl(
+            $nav,
             route('spare_parts.index', session('gp_spare_parts'))
         );
-        return redirect()->to($previous_url)->with(['success' => 'Запчасть успешно обновлена!']);
+        return redirect()->to($backUrl)->with(['success' => 'Запчасть успешно обновлена!']);
     }
 
     protected function syncPricingCodes(SparePart $sparePart, StoreSparePartRequest $request): void
@@ -299,11 +308,11 @@ class SparePartController extends Controller
 
             Cache::forget('spare_part_image:' . $sparePart->article);
 
-            return redirect()->route('spare_parts.show', $sparePart)
+            return $this->redirectToSparePartShow($request, $sparePart)
                 ->with(['success' => 'Изображение успешно загружено!']);
         }
 
-        return redirect()->route('spare_parts.show', $sparePart)
+        return $this->redirectToSparePartShow($request, $sparePart)
             ->with(['error' => 'Ошибка загрузки изображения!']);
     }
 
@@ -326,4 +335,11 @@ class SparePartController extends Controller
         return response()->json($spareParts);
     }
 
+    private function redirectToSparePartShow(Request $request, SparePart $sparePart): RedirectResponse
+    {
+        $nav = $this->resolveNavToken($request);
+
+        return redirect()->route('spare_parts.show', $this->withNav(['sparePart' => $sparePart], $nav));
+    }
+
 }

+ 35 - 19
app/Http/Controllers/SparePartOrderController.php

@@ -48,6 +48,8 @@ class SparePartOrderController extends Controller
     public function index(Request $request)
     {
         session(['gp_spare_part_orders' => $request->query()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
         $model = new SparePartOrdersView();
 
         // Фильтры
@@ -98,15 +100,19 @@ class SparePartOrderController extends Controller
             ->pluck('order_number');
         $this->data['strings'] = $this->data['spare_part_orders'];
         $this->data['tab'] = 'orders';
+        $this->data['nav'] = $nav;
 
         return view('spare_parts.index', $this->data);
     }
 
     public function show(Request $request, SparePartOrder $sparePartOrder)
     {
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_spare_part_orders',
+            $nav,
             route('spare_part_orders.index', session('gp_spare_part_orders'))
         );
         $this->data['spare_part_order'] = $sparePartOrder->load([
@@ -119,9 +125,12 @@ class SparePartOrderController extends Controller
 
     public function create(Request $request)
     {
-        $this->data['previous_url'] = $this->resolvePreviousUrl(
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
             $request,
-            'previous_url_spare_part_orders',
+            $nav,
             route('spare_part_orders.index', session('gp_spare_part_orders'))
         );
         $this->data['spare_part_order'] = null;
@@ -136,12 +145,12 @@ class SparePartOrderController extends Controller
         // Observer автоматически обработает дефициты при создании партии со статусом in_stock
         SparePartOrder::create($data);
 
-        $previous_url = $this->previousUrlForRedirect(
-            $request,
-            'previous_url_spare_part_orders',
+        $nav = $this->resolveNavToken($request);
+        $backUrl = $this->navigationParentUrl(
+            $nav,
             route('spare_part_orders.index', session('gp_spare_part_orders'))
         );
-        return redirect()->to($previous_url)->with(['success' => 'Заказ детали успешно создан!']);
+        return redirect()->to($backUrl)->with(['success' => 'Заказ детали успешно создан!']);
     }
 
     public function update(StoreSparePartOrderRequest $request, SparePartOrder $sparePartOrder): RedirectResponse
@@ -149,19 +158,19 @@ class SparePartOrderController extends Controller
         // Observer автоматически обработает дефициты при смене статуса на in_stock
         $sparePartOrder->update($request->validated());
 
-        $previous_url = $this->previousUrlForRedirect(
-            $request,
-            'previous_url_spare_part_orders',
+        $nav = $this->resolveNavToken($request);
+        $backUrl = $this->navigationParentUrl(
+            $nav,
             route('spare_part_orders.index', session('gp_spare_part_orders'))
         );
-        return redirect()->to($previous_url)->with(['success' => 'Заказ детали успешно обновлён!']);
+        return redirect()->to($backUrl)->with(['success' => 'Заказ детали успешно обновлён!']);
     }
 
-    public function destroy(SparePartOrder $sparePartOrder): RedirectResponse
+    public function destroy(Request $request, SparePartOrder $sparePartOrder): RedirectResponse
     {
         // Проверяем, есть ли активные резервы
         if ($sparePartOrder->reservations()->where('status', 'active')->exists()) {
-            return redirect()->route('spare_part_orders.show', $sparePartOrder)
+            return $this->redirectToSparePartOrderShow($request, $sparePartOrder)
                 ->with(['error' => 'Невозможно удалить заказ с активными резервами!']);
         }
 
@@ -185,10 +194,10 @@ class SparePartOrderController extends Controller
                 $validated['note']
             );
 
-            return redirect()->route('spare_part_orders.show', $sparePartOrder)
+            return $this->redirectToSparePartOrderShow($request, $sparePartOrder)
                 ->with(['success' => 'Отгрузка успешно выполнена!']);
         } catch (\InvalidArgumentException $e) {
-            return redirect()->route('spare_part_orders.show', $sparePartOrder)
+            return $this->redirectToSparePartOrderShow($request, $sparePartOrder)
                 ->with(['error' => $e->getMessage()]);
         }
     }
@@ -201,7 +210,7 @@ class SparePartOrderController extends Controller
         // Observer автоматически обработает дефициты при смене статуса
         $sparePartOrder->update(['status' => SparePartOrder::STATUS_IN_STOCK]);
 
-        return redirect()->route('spare_part_orders.show', $sparePartOrder)
+        return $this->redirectToSparePartOrderShow(request(), $sparePartOrder)
             ->with(['success' => 'Статус изменён на "На складе"!']);
     }
 
@@ -250,11 +259,18 @@ class SparePartOrderController extends Controller
                 $validated['reason']
             );
 
-            return redirect()->route('spare_part_orders.show', $sparePartOrder)
+            return $this->redirectToSparePartOrderShow($request, $sparePartOrder)
                 ->with(['success' => 'Коррекция выполнена!']);
         } catch (\InvalidArgumentException $e) {
-            return redirect()->route('spare_part_orders.show', $sparePartOrder)
+            return $this->redirectToSparePartOrderShow($request, $sparePartOrder)
                 ->with(['error' => $e->getMessage()]);
         }
     }
+
+    private function redirectToSparePartOrderShow(Request $request, SparePartOrder $sparePartOrder): RedirectResponse
+    {
+        $nav = $this->resolveNavToken($request);
+
+        return redirect()->route('spare_part_orders.show', $this->withNav(['sparePartOrder' => $sparePartOrder], $nav));
+    }
 }

+ 21 - 2
app/Http/Controllers/UserController.php

@@ -46,6 +46,8 @@ class UserController extends Controller
     public function index(Request $request)
     {
         session(['gp_users' => $request->query()]);
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
 
         $model = new User;
         $this->createFilters($model, 'role');
@@ -64,6 +66,7 @@ class UserController extends Controller
 //        $q->withTrashed();
         $this->applyStableSorting($q);
         $this->data['users'] = $q->paginate($this->data['per_page'])->withQueryString();
+        $this->data['nav'] = $nav;
 
         return view('users.index', $this->data);
     }
@@ -71,8 +74,16 @@ class UserController extends Controller
     /**
      * Show the form for creating a new resource.
      */
-    public function create()
+    public function create(Request $request)
     {
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
+            $request,
+            $nav,
+            route('user.index', session('gp_users', []))
+        );
         $this->data['user'] = null;
         $this->prepareNotificationSettingsData(null);
         return view('users.edit', $this->data);
@@ -117,8 +128,16 @@ class UserController extends Controller
     /**
      * Display the specified resource.
      */
-    public function show(int $userId)
+    public function show(Request $request, int $userId)
     {
+        $nav = $this->resolveNavToken($request);
+        $this->rememberNavigation($request, $nav);
+        $this->data['nav'] = $nav;
+        $this->data['back_url'] = $this->navigationBackUrl(
+            $request,
+            $nav,
+            route('user.index', session('gp_users', []))
+        );
         $this->data['user'] = User::query()
             ->where('id', $userId)
             ->withTrashed()

+ 202 - 0
app/Services/NavigationContextService.php

@@ -0,0 +1,202 @@
+<?php
+
+namespace App\Services;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Str;
+
+class NavigationContextService
+{
+    private const SESSION_KEY = 'navigation';
+    private const MAX_CONTEXTS = 30;
+    private const MAX_STACK_SIZE = 20;
+    private const TTL_SECONDS = 86400;
+
+    public function getOrCreateToken(Request $request): string
+    {
+        $this->pruneExpired();
+
+        $token = trim((string) $request->input('nav', ''));
+        if ($token !== '' && $this->contextExists($token)) {
+            return $token;
+        }
+
+        $token = bin2hex(random_bytes(8));
+        $this->saveContext($token, [
+            'updated_at' => time(),
+            'stack' => [],
+        ]);
+
+        return $token;
+    }
+
+    public function rememberCurrentPage(Request $request, string $token): void
+    {
+        if (!$this->isGetPage($request)) {
+            return;
+        }
+
+        $this->appendUrl($token, $request->fullUrl());
+    }
+
+    public function backUrl(Request $request, string $token, ?string $fallback = null): ?string
+    {
+        $context = $this->context($token);
+        $stack = $context['stack'] ?? [];
+
+        if (empty($stack)) {
+            return $fallback;
+        }
+
+        $currentUrl = $this->normalizeUrl($request->fullUrl());
+        $lastUrl = end($stack);
+
+        if ($lastUrl === $currentUrl) {
+            $previousUrl = prev($stack);
+            return $previousUrl !== false ? $previousUrl : $fallback;
+        }
+
+        return $lastUrl ?: $fallback;
+    }
+
+    public function parentUrl(string $token, ?string $fallback = null): ?string
+    {
+        $context = $this->context($token);
+        $stack = $context['stack'] ?? [];
+
+        if (empty($stack)) {
+            return $fallback;
+        }
+
+        $lastUrl = array_pop($stack);
+        if (empty($stack)) {
+            return $fallback ?? $lastUrl;
+        }
+
+        return end($stack) ?: $fallback;
+    }
+
+    public function routeParams(array $params, string $token): array
+    {
+        return array_merge($params, ['nav' => $token]);
+    }
+
+    public function pruneExpired(): void
+    {
+        $contexts = session(self::SESSION_KEY, []);
+        if (empty($contexts)) {
+            return;
+        }
+
+        $now = time();
+        foreach ($contexts as $token => $context) {
+            $updatedAt = (int) ($context['updated_at'] ?? 0);
+            if ($updatedAt < ($now - self::TTL_SECONDS)) {
+                unset($contexts[$token]);
+            }
+        }
+
+        if (count($contexts) > self::MAX_CONTEXTS) {
+            uasort($contexts, static fn (array $left, array $right) => ($left['updated_at'] ?? 0) <=> ($right['updated_at'] ?? 0));
+            $contexts = array_slice($contexts, -self::MAX_CONTEXTS, null, true);
+        }
+
+        session([self::SESSION_KEY => $contexts]);
+    }
+
+    public function forgetToken(string $token): void
+    {
+        $contexts = session(self::SESSION_KEY, []);
+        unset($contexts[$token]);
+        session([self::SESSION_KEY => $contexts]);
+    }
+
+    private function appendUrl(string $token, string $url): void
+    {
+        $normalizedUrl = $this->normalizeUrl($url);
+        if ($normalizedUrl === '') {
+            return;
+        }
+
+        $context = $this->context($token);
+        $stack = $context['stack'] ?? [];
+
+        $stack = array_values(array_filter($stack, static fn (string $storedUrl) => $storedUrl !== $normalizedUrl));
+        $stack[] = $normalizedUrl;
+
+        if (count($stack) > self::MAX_STACK_SIZE) {
+            $stack = array_slice($stack, -self::MAX_STACK_SIZE);
+        }
+
+        $context['updated_at'] = time();
+        $context['stack'] = $stack;
+
+        $this->saveContext($token, $context);
+    }
+
+    private function normalizeUrl(string $url): string
+    {
+        $parts = parse_url($url);
+        if ($parts === false || empty($parts['path'])) {
+            return '';
+        }
+
+        $query = [];
+        if (!empty($parts['query'])) {
+            parse_str($parts['query'], $query);
+            unset($query['nav']);
+        }
+
+        $normalizedUrl = '';
+        if (!empty($parts['scheme'])) {
+            $normalizedUrl .= $parts['scheme'] . '://';
+        }
+
+        if (!empty($parts['user'])) {
+            $normalizedUrl .= $parts['user'];
+            if (!empty($parts['pass'])) {
+                $normalizedUrl .= ':' . $parts['pass'];
+            }
+            $normalizedUrl .= '@';
+        }
+
+        if (!empty($parts['host'])) {
+            $normalizedUrl .= $parts['host'];
+        }
+
+        if (!empty($parts['port'])) {
+            $normalizedUrl .= ':' . $parts['port'];
+        }
+
+        $normalizedUrl .= $parts['path'];
+
+        if (!empty($query)) {
+            $normalizedUrl .= '?' . http_build_query($query);
+        }
+
+        return $normalizedUrl;
+    }
+
+    private function isGetPage(Request $request): bool
+    {
+        return strtoupper($request->method()) === 'GET';
+    }
+
+    private function contextExists(string $token): bool
+    {
+        return array_key_exists($token, session(self::SESSION_KEY, []));
+    }
+
+    private function context(string $token): array
+    {
+        return session(self::SESSION_KEY . '.' . $token, [
+            'updated_at' => time(),
+            'stack' => [],
+        ]);
+    }
+
+    private function saveContext(string $token, array $context): void
+    {
+        session([self::SESSION_KEY . '.' . $token => $context]);
+    }
+}

+ 6 - 5
resources/views/admin/areas/edit.blade.php

@@ -19,8 +19,9 @@
                 </div>
             @endif
 
-            <form action="{{ route('admin.area.store') }}" method="post">
+            <form action="{{ route('admin.area.store', ['nav' => $nav ?? null]) }}" method="post">
                 @csrf
+                <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                 <input type="hidden" name="id" value="{{ $area->id }}">
 
                 @include('partials.input', [
@@ -43,21 +44,21 @@
                         <a href="#" class="btn btn-sm btn-warning undelete">Восстановить</a>
                     </div>
                 @else
-                    @include('partials.submit', ['delete' => ['form_id' => 'delete-area']])
+                    @include('partials.submit', ['delete' => ['form_id' => 'delete-area'], 'back_url' => $back_url ?? route('admin.area.index')])
                 @endif
             </form>
 
-            <form action="{{ route('admin.area.undelete', $area->id) }}" method="post" class="d-none" id="undelete-area">
+            <form action="{{ route('admin.area.undelete', ['area' => $area->id, 'nav' => $nav ?? null]) }}" method="post" class="d-none" id="undelete-area">
                 @csrf
             </form>
 
-            <form action="{{ route('admin.area.destroy', $area->id) }}" method="post" class="d-none" id="delete-area">
+            <form action="{{ route('admin.area.destroy', ['area' => $area->id, 'nav' => $nav ?? null]) }}" method="post" class="d-none" id="delete-area">
                 @method('DELETE')
                 @csrf
             </form>
 
             <div class="mt-3">
-                <a href="{{ route('admin.area.index') }}" class="btn btn-sm btn-outline-secondary">
+                <a href="{{ $back_url ?? route('admin.area.index') }}" class="btn btn-sm btn-outline-secondary">
                     &larr; Назад к списку
                 </a>
             </div>

+ 1 - 0
resources/views/admin/areas/index.blade.php

@@ -46,6 +46,7 @@
         'ranges' => [],
         'dates' => [],
         'enableColumnFilters' => false,
+        'nav' => $nav ?? null,
     ])
 
     <!-- Модальное окно добавления -->

+ 6 - 5
resources/views/admin/districts/edit.blade.php

@@ -19,8 +19,9 @@
                 </div>
             @endif
 
-            <form action="{{ route('admin.district.store') }}" method="post">
+            <form action="{{ route('admin.district.store', ['nav' => $nav ?? null]) }}" method="post">
                 @csrf
+                <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                 <input type="hidden" name="id" value="{{ $district->id }}">
 
                 @include('partials.input', [
@@ -42,21 +43,21 @@
                         <a href="#" class="btn btn-sm btn-warning undelete">Восстановить</a>
                     </div>
                 @else
-                    @include('partials.submit', ['delete' => ['form_id' => 'delete-district']])
+                    @include('partials.submit', ['delete' => ['form_id' => 'delete-district'], 'back_url' => $back_url ?? route('admin.district.index')])
                 @endif
             </form>
 
-            <form action="{{ route('admin.district.undelete', $district->id) }}" method="post" class="d-none" id="undelete-district">
+            <form action="{{ route('admin.district.undelete', ['district' => $district->id, 'nav' => $nav ?? null]) }}" method="post" class="d-none" id="undelete-district">
                 @csrf
             </form>
 
-            <form action="{{ route('admin.district.destroy', $district->id) }}" method="post" class="d-none" id="delete-district">
+            <form action="{{ route('admin.district.destroy', ['district' => $district->id, 'nav' => $nav ?? null]) }}" method="post" class="d-none" id="delete-district">
                 @method('DELETE')
                 @csrf
             </form>
 
             <div class="mt-3">
-                <a href="{{ route('admin.district.index') }}" class="btn btn-sm btn-outline-secondary">
+                <a href="{{ $back_url ?? route('admin.district.index') }}" class="btn btn-sm btn-outline-secondary">
                     &larr; Назад к списку
                 </a>
             </div>

+ 1 - 0
resources/views/admin/districts/index.blade.php

@@ -31,6 +31,7 @@
         'ranges' => [],
         'dates' => [],
         'enableColumnFilters' => false,
+        'nav' => $nav ?? null,
     ])
 
     <!-- Модальное окно добавления -->

+ 4 - 4
resources/views/catalog/edit.blade.php

@@ -15,14 +15,14 @@
                     @endif
                     <button class="btn btn-sm text-success" onclick="$('#upl-thumb').trigger('click');" title="Загрузить изображение"><i class="bi bi-image"></i> Изображение</button>
 
-                    <form action="{{ route('catalog.upload-thumbnail', ['product' => $product, 'previous_url' => $previous_url ?? '']) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
+                    <form action="{{ route('catalog.upload-thumbnail', ['product' => $product, 'nav' => $nav ?? null]) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
                         @csrf
                         <input type="file" name="thumbnail" accept=".jpg,.jpeg" onchange="$(this).parent().submit()" required id="upl-thumb" />
                     </form>
 
                     <button class="btn btn-sm text-success" onclick="$('#upl-cert').trigger('click');"><i class="bi bi-plus-circle-fill"></i> Загрузить сертификат</button>
 
-                    <form action="{{ route('catalog.upload-certificate', ['product' => $product, 'previous_url' => $previous_url ?? '']) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
+                    <form action="{{ route('catalog.upload-certificate', ['product' => $product, 'nav' => $nav ?? null]) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
                         @csrf
                         <input type="file" name="certificate" onchange="$(this).parent().submit()" required id="upl-cert" />
 
@@ -53,7 +53,7 @@
                         @include('partials.input', ['name' => 'statement_name', 'title' => 'Наименование в ведомости', 'value' => $product->statement_name ?? '', 'disabled' => !hasRole('admin')])
                         @include('partials.input', ['name' => 'service_life', 'title' => 'Срок службы', 'type' => 'number', 'value' => $product?->service_life, 'disabled' => !hasRole('admin')])
 
-                        <input type="hidden" name="previous_url" value="{{ $previous_url ?? '' }}">
+                        <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                     </div>
                     <div class="col-xl-6">
                         @if($product?->certificate)
@@ -81,7 +81,7 @@
                         </div>
                     </div>
                     <div class="col-12">
-                        @include('partials.submit', ['deleteDisabled' => (!isset($product) || $product->hasRelations() || !hasRole('admin')), 'disabled' => !hasRole('admin'), 'offset' => 6, 'delete' => ['form_id' => 'deleteProduct']])
+                        @include('partials.submit', ['deleteDisabled' => (!isset($product) || $product->hasRelations() || !hasRole('admin')), 'disabled' => !hasRole('admin'), 'offset' => 6, 'delete' => ['form_id' => 'deleteProduct'], 'back_url' => $back_url ?? route('catalog.index', session('gp_products'))])
                     </div>
                 </div>
 

+ 4 - 3
resources/views/contracts/edit.blade.php

@@ -3,9 +3,10 @@
 @section('content')
     <div class="px-3">
         <div class="col-xxl-6 offset-xxl-2">
-            <form action="{{ ($contract) ? route('contract.update', $contract) : route('contract.store') }}" method="post">
+            <form action="{{ ($contract) ? route('contract.update', ['contract' => $contract, 'nav' => $nav ?? null]) : route('contract.store', ['nav' => $nav ?? null]) }}" method="post">
 
                 @csrf
+                <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                 @include('partials.input', ['name' => 'year',
                                             'type' => 'number',
                                             'title' => 'Год',
@@ -15,11 +16,11 @@
                 @include('partials.input', ['name' => 'contract_number', 'title' => 'Номер договора', 'required' => true, 'value' => $contract->contract_number ?? ''])
                 @include('partials.input', ['name' => 'contract_date', 'type' => 'date','title' => 'Дата договора', 'value' => $contract->contract_date ?? date('Y-m-d'), 'required' => true])
 
-                @include('partials.submit', ['deleteDisabled' => (!isset($contract)), 'delete' => ['form_id' => 'deleteContract']])
+                @include('partials.submit', ['deleteDisabled' => (!isset($contract)), 'delete' => ['form_id' => 'deleteContract'], 'back_url' => $back_url ?? route('contract.index', session('gp_contracts', []))])
             </form>
             @if($contract)
                 <div class="visually-hidden">
-                    <form action="{{ route('contract.delete', $contract) }}" method="POST" id="deleteContract">
+                    <form action="{{ route('contract.delete', ['contract' => $contract, 'nav' => $nav ?? null]) }}" method="POST" id="deleteContract">
                         @csrf
                         @method('DELETE')
                     </form>

+ 2 - 1
resources/views/contracts/index.blade.php

@@ -6,7 +6,7 @@
             <h3>Договоры</h3>
         </div>
         <div class="col-md-6 text-end">
-            <a href="{{ route('contract.create') }}" class="btn btn-sm btn-primary">Добавить</a>
+            <a href="{{ route('contract.create', ['nav' => $nav ?? null]) }}" class="btn btn-sm btn-primary">Добавить</a>
         </div>
     </div>
     @include('partials.table', [
@@ -15,6 +15,7 @@
         'strings'   => $contracts,
         'routeName' => 'contract.show',
         'routeParam' => 'contract',
+        'nav' => $nav ?? null,
     ])
 
     @include('partials.pagination', ['items' => $contracts])

+ 1 - 1
resources/views/import/show.blade.php

@@ -15,7 +15,7 @@
             </div>
             <div class="col-md-6 text-end">
 
-                <a href="{{ $previous_url ?? route('import.index', session('gp_import')) }}"
+                <a href="{{ $back_url ?? route('import.index', session('gp_import')) }}"
                    class="btn btn-sm mb-1 btn-outline-secondary">Назад</a>
 
             </div>

+ 4 - 4
resources/views/maf_orders/edit.blade.php

@@ -21,11 +21,11 @@
                     @if($maf_order->products_sku->count())
                         <div class="row">
                             <div class="buttons offset-md-4 col-md-8 ">
-                                <a href="{{ $previous_url ?? route('maf_order.index', session('gp_maf_order')) }}" class="btn btn-sm btn-primary">Назад</a>
+                                <a href="{{ $back_url ?? route('maf_order.index', session('gp_maf_order')) }}" class="btn btn-sm btn-primary">Назад</a>
                             </div>
                         </div>
                     @else
-                        @include('partials.submit', ['name' => 'Сохранить', 'deleteDisabled' => $maf_order->products_sku->count(), 'delete' => ['form_id' => 'destroy', 'title' => 'Удалить']])
+                        @include('partials.submit', ['name' => 'Сохранить', 'deleteDisabled' => $maf_order->products_sku->count(), 'delete' => ['form_id' => 'destroy', 'title' => 'Удалить'], 'back_url' => $back_url ?? route('maf_order.index', session('gp_maf_order'))])
                         @if($maf_order->status == 'заказан')
                             <div class="row mt-3">
                                 <div class="buttons offset-md-4 col-md-8 ">
@@ -44,7 +44,7 @@
             <h3>Площадки, куда отгружен МАФ</h3>
             @foreach($maf_order->products_sku as $product_sku)
                 <div>
-                    <a href="{{ route('order.show', $product_sku->order) }}">
+                    <a href="{{ route('order.show', ['order' => $product_sku->order, 'nav' => $nav ?? null]) }}">
                         {{ $product_sku->order->common_name }}
                     </a>
                 </div>
@@ -54,7 +54,7 @@
     </div>
 
     <div class="visually-hidden d-none">
-        <form action="{{ route('maf_order.set_in_stock', $maf_order) }}" method="post" id="set-status">
+        <form action="{{ route('maf_order.set_in_stock', ['maf_order' => $maf_order, 'nav' => $nav ?? null]) }}" method="post" id="set-status">
             @csrf
         </form>
         <form action="{{ route('maf_order.delete', $maf_order) }}" id="destroy" method="post">

+ 2 - 1
resources/views/orders/edit.blade.php

@@ -13,6 +13,7 @@
                 @if(isset($order))
                     <input type="hidden" name="id" value="{{ $order->id }}">
                 @endif
+                <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
 
                 @include('partials.select', ['name' => 'order_status_id', 'title' => 'Статус', 'options' => $orderStatuses, 'value' => $order->order_status_id ?? old('order_status_id'), 'required' => true])
 
@@ -87,7 +88,7 @@
 
             </div>
             <div class="col-12 text-end">
-                @include('partials.submit')
+                @include('partials.submit', ['back_url' => $back_url ?? route('order.index', session('gp_orders'))])
             </div>
         </form>
 

+ 5 - 5
resources/views/orders/show.blade.php

@@ -16,7 +16,7 @@
             </div>
             <div class="col-md-6 action-toolbar">
                 @if(hasRole('admin,manager'))
-                    <a href="{{ route('order.edit', ['order' => $order, 'previous_url' => $previous_url]) }}"
+                    <a href="{{ route('order.edit', ['order' => $order, 'nav' => $nav ?? null]) }}"
                        class="btn btn-sm mb-1 btn-primary">Редактировать</a>
                 @endif
                 @if(hasRole('admin'))
@@ -47,7 +47,7 @@
                         для сдачи</a>
                 @endif
 
-                <a href="{{ $previous_url ?? route('order.index', session('gp_orders')) }}"
+                <a href="{{ $back_url ?? route('order.index', session('gp_orders')) }}"
                    class="btn btn-sm mb-1 btn-outline-secondary">Назад</a>
 
             </div>
@@ -78,7 +78,7 @@
                         Рекламации
                         @foreach($order->reclamations as $reclamation)
                             <div>
-                                <a href="{{ route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => url()->current()]) }}">
+                                <a href="{{ route('reclamations.show', ['reclamation' => $reclamation, 'nav' => $nav ?? null]) }}">
                                     Рекламация № {{ $reclamation->id }} от {{ $reclamation->create_date }}
                                 </a>
                             </div>
@@ -267,12 +267,12 @@
                                         </td>
                                         <td>
                                             @if(hasRole('admin'))
-                                                <a href="{{ route('product_sku.show', ['product_sku' =>$p, 'previous_url' => url()->current()]) }}">
+                                                <a href="{{ route('product_sku.show', ['product_sku' =>$p, 'nav' => $nav ?? null]) }}">
                                                     {!! $p->product->article !!}
                                                 </a>
                                                 <br>
                                                 <a class="small"
-                                                   href="{{ route('catalog.show', ['product' => $p->product, 'previous_url' => request()->fullUrl()]) }}">каталог</a>
+                                                   href="{{ route('catalog.show', ['product' => $p->product, 'nav' => $nav ?? null]) }}">каталог</a>
                                             @else
                                                 {!! $p->product->article !!}
                                             @endif

+ 1 - 1
resources/views/partials/submit.blade.php

@@ -5,7 +5,7 @@
             <a href="#" class="btn btn-sm mb-1 btn-danger delete">{{ $delete['title'] ?? 'Удалить' }}</a>
         @endif
 
-        <a href="{!! $backurl ?? ($previous_url ?? url()->previous()) !!}" class="btn btn-sm mb-1 btn-outline-secondary">Назад</a>
+        <a href="{!! $backurl ?? ($back_url ?? url()->previous()) !!}" class="btn btn-sm mb-1 btn-outline-secondary">Назад</a>
 
     </div>
 </div>

+ 2 - 2
resources/views/partials/table.blade.php

@@ -88,8 +88,8 @@
                 $rowId = $string->id ?? null;
                 $rowAnchor = $rowId ? 'row-' . $rowId : null;
                 $rowHref = null;
-                if (isset($routeName) && $rowId) {
-                    $rowHref = route($routeName, [$string->id, 'previous_url' => request()->fullUrl() . '#' . $rowAnchor]);
+                if (isset($routeName) && $rowId && !empty($nav ?? null)) {
+                    $rowHref = route($routeName, [$string->id, 'nav' => $nav]);
                 }
             @endphp
             <tr

+ 4 - 4
resources/views/products_sku/edit.blade.php

@@ -11,7 +11,7 @@
                 @if(isset($product_sku) && hasRole('admin'))
                     <button class="btn btn-sm text-success" onclick="$('#upl-pass').trigger('click');"><i class="bi bi-plus-circle-fill"></i> Загрузить паспорт</button>
 
-                    <form action="{{ route('product-sku.upload-passport', ['product_sku' => $product_sku, 'previous_url' => $previous_url ?? '']) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
+                    <form action="{{ route('product-sku.upload-passport', ['product_sku' => $product_sku, 'nav' => $nav ?? null]) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
                         @csrf
                         <input type="file" name="passport" onchange="$(this).parent().submit()" required id="upl-pass" />
                     </form>
@@ -25,9 +25,9 @@
                 @csrf
 
                 <input type="hidden" id="product_id" name="product_id" value="{{ $product_sku->product_id }}">
-                <input type="hidden" name="previous_url" value="{{ $previous_url ?? '' }}">
+                <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                 @include('partials.input', ['name' => 'year', 'title' => 'Год', 'value' => $product_sku->year, 'disabled' => true])
-                @include('partials.link',  ['href' => route('order.show', $product_sku->order_id), 'title' => 'Площадка', 'text' => $product_sku->order->common_name])
+                @include('partials.link',  ['href' => route('order.show', ['order' => $product_sku->order_id, 'nav' => $nav ?? null]), 'title' => 'Площадка', 'text' => $product_sku->order->common_name])
                 @include('partials.input', ['name' => 'product_name', 'title' => 'МАФ', 'disabled' => true, 'value' => $product_sku->product->common_name])
                 @include('partials.input', ['name' => 'rfid', 'title' => 'RFID', 'required' => true, 'disabled' => !hasRole('admin'), 'value' => $product_sku->rfid])
                 @include('partials.input', ['name' => 'factory_number', 'title' => 'Номер фабрики', 'required' => true, 'disabled' => !hasRole('admin'), 'value' => $product_sku->factory_number])
@@ -54,7 +54,7 @@
 
             </div>
             <div class="col-12">
-                @include('partials.submit', ['name' => 'Сохранить', 'offset' => 5, 'disabled' => !hasRole('admin')])
+                @include('partials.submit', ['name' => 'Сохранить', 'offset' => 5, 'disabled' => !hasRole('admin'), 'back_url' => $back_url ?? route('product_sku.index', session('gp_product_sku'))])
             </div>
         </form>
 @endsection

+ 20 - 11
resources/views/reclamations/edit.blade.php

@@ -13,15 +13,15 @@
                 </h4>
             </div>
             <div class="col-xl-6 action-toolbar mb-2">
-                <a href="{{ $previous_url ?? route('reclamations.index', session('gp_reclamations')) }}"
+                <a href="{{ $back_url ?? route('reclamations.index', session('gp_reclamations')) }}"
                    class="btn btn-sm btn-outline-secondary">Назад</a>
                 @if(hasRole('admin') && !is_null($reclamation->brigadier_id) && !is_null($reclamation->start_work_date))
                     <button class="btn btn-sm btn-primary" id="createScheduleButton">Перенести в график</button>
                 @endif
                 @if(hasRole('admin,manager'))
-                    <a href="{{ route('order.generate-reclamation-pack', $reclamation) }}"
+                    <a href="{{ route('order.generate-reclamation-pack', ['reclamation' => $reclamation, 'nav' => $nav ?? null]) }}"
                        class="btn btn-primary btn-sm">Пакет документов рекламации</a>
-                    <a href="{{ route('reclamation.generate-reclamation-payment-pack', $reclamation) }}"
+                    <a href="{{ route('reclamation.generate-reclamation-payment-pack', ['reclamation' => $reclamation, 'nav' => $nav ?? null]) }}"
                        class="btn btn-primary btn-sm">Пакет документов на оплату</a>
                 @endif
                 @if(hasRole('admin'))
@@ -40,9 +40,9 @@
 
                     @csrf
                     <input type="hidden" id="order_id" name="order_id" value="{{ $reclamation->order_id}}">
-                    <input type="hidden" name="previous_url" value="{{ $previous_url ?? '' }}">
+                    <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
 
-                    @include('partials.link', ['title' => 'Площадка', 'href' => route('order.show', ['order' => $reclamation->order_id, 'sync_year' => 1]), 'text' => $reclamation->order->common_name ?? ''])
+                    @include('partials.link', ['title' => 'Площадка', 'href' => route('order.show', ['order' => $reclamation->order_id, 'sync_year' => 1, 'nav' => $nav ?? null]), 'text' => $reclamation->order->common_name ?? ''])
                     @include('partials.select', ['name' => 'status_id', 'title' => 'Статус', 'options' => $statuses, 'value' => $reclamation->status_id ?? old('status_id'), 'disabled' => !hasRole('admin,manager'), 'classes' => ['update-once']])
                     @include('partials.select', ['name' => 'user_id', 'title' => 'Менеджер', 'options' => $users, 'value' => $reclamation->user_id ?? old('user_id') ?? auth()->user()->id, 'disabled' => !hasRole('admin,manager'), 'classes' => ['update-once']])
                     @include('partials.input', ['name' => 'maf_installation_year', 'title' => 'Год установки МАФ', 'type' => 'text', 'value' => $reclamation->order->year, 'disabled' => true])
@@ -56,7 +56,7 @@
                     @include('partials.textarea', ['name' => 'guarantee', 'title' => 'Гарантии', 'size' => 6, 'value' => $reclamation->guarantee ?? '', 'disabled' => !hasRole('admin,manager'), 'classes' => ['update-once']])
                     @include('partials.textarea', ['name' => 'whats_done', 'title' => 'Что сделано', 'size' => 6, 'value' => $reclamation->whats_done ?? '', 'disabled' => !hasRole('admin,manager'), 'classes' => ['update-once']])
                     @include('partials.textarea', ['name' => 'comment', 'title' => 'Комментарий', 'size' => 6, 'value' => $reclamation->comment ?? '', 'disabled' => !hasRole('admin,manager'), 'classes' => ['update-once']])
-                    @include('partials.submit', ['name' => 'Сохранить', 'offset' => 5, 'disabled' => !hasRole('admin,manager'), 'backurl' => route('reclamations.index', session('gp_reclamations'))])
+                    @include('partials.submit', ['name' => 'Сохранить', 'offset' => 5, 'disabled' => !hasRole('admin,manager'), 'back_url' => $back_url ?? route('reclamations.index', session('gp_reclamations'))])
                 </form>
             </div>
             <div class="col-xl-7 ">
@@ -86,11 +86,11 @@
                                 </td>
                                 <td>
                                     @if(hasRole('admin,manager'))
-                                        <a href="{{ route('product_sku.show', $p) }}">
+                                        <a href="{{ route('product_sku.show', ['product_sku' => $p, 'nav' => $nav ?? null]) }}">
                                             {{ $p->product->article }}
                                         </a>
                                         <br>
-                                        <a class="small" href="{{ route('catalog.show', $p->product) }}">каталог</a>
+                                        <a class="small" href="{{ route('catalog.show', ['product' => $p->product, 'nav' => $nav ?? null]) }}">каталог</a>
                                     @else
                                         {{ $p->product->article }}
                                     @endif
@@ -128,6 +128,7 @@
                     <form method="post" action="{{ route('reclamations.update-spare-parts', $reclamation) }}"
                           class="my-2 collapse" id="spare_parts">
                         @csrf
+                        <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                         <div class="spare-parts-rows">
                             @forelse($reclamation->spareParts as $idx => $sp)
                                 <div class="row mb-1 spare-part-row g-1" data-index="{{ $idx }}">
@@ -531,6 +532,7 @@
                         <form action="{{ route('reclamations.upload-document', $reclamation) }}"
                               enctype="multipart/form-data" method="post" class="visually-hidden">
                             @csrf
+                            <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                             <input required type="file" id="upl-documents" onchange="$(this).parent().submit()" multiple
                                    name="document[]" class="form-control form-control-sm">
                         </form>
@@ -550,6 +552,7 @@
                                       method="POST" id="document-{{ $document->id }}" class="visually-hidden">
                                     @csrf
                                     @method('DELETE')
+                                    <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                                 </form>
                             </div>
                         @endforeach
@@ -565,6 +568,7 @@
                         <form action="{{ route('reclamations.upload-act', $reclamation) }}" enctype="multipart/form-data"
                               method="post" class="visually-hidden">
                             @csrf
+                            <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                             <input required type="file" id="upl-acts" onchange="$(this).parent().submit()" multiple
                                    name="acts[]" class="form-control form-control-sm">
                         </form>
@@ -585,6 +589,7 @@
                                       id="act-{{ $act->id }}" class="visually-hidden">
                                     @csrf
                                     @method('DELETE')
+                                    <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                                 </form>
                             </div>
                         @endforeach
@@ -600,7 +605,7 @@
                         </button>
                     @endif
                     @if($reclamation->photos_before->count())
-                        <a href="{{ route('reclamation.generate-photos-before-pack', $reclamation) }}"
+                        <a href="{{ route('reclamation.generate-photos-before-pack', ['reclamation' => $reclamation, 'nav' => $nav ?? null]) }}"
                            class="btn btn-sm text-primary"><i
                                     class="bi bi-download"></i> Скачать все
                         </a>
@@ -609,6 +614,7 @@
                         <form action="{{ route('reclamations.upload-photo-before', $reclamation) }}"
                               enctype="multipart/form-data" method="post" class="visually-hidden">
                             @csrf
+                            <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                             <input required type="file" id="upl-photo-before" onchange="$(this).parent().submit()" multiple
                                    name="photo[]" class="form-control form-control-sm" accept=".jpg,.jpeg,.png,.webp">
                         </form>
@@ -629,6 +635,7 @@
                                       method="POST" id="photo-{{ $photo->id }}" class="visually-hidden">
                                     @csrf
                                     @method('DELETE')
+                                    <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                                 </form>
                             </div>
                         @endforeach
@@ -644,7 +651,7 @@
                         </button>
                     @endif
                     @if($reclamation->photos_after->count())
-                        <a href="{{ route('reclamation.generate-photos-after-pack', $reclamation) }}"
+                        <a href="{{ route('reclamation.generate-photos-after-pack', ['reclamation' => $reclamation, 'nav' => $nav ?? null]) }}"
                            class="btn btn-sm text-primary"><i
                                     class="bi bi-download"></i> Скачать все
                         </a>
@@ -654,6 +661,7 @@
                         <form action="{{ route('reclamations.upload-photo-after', $reclamation) }}"
                               enctype="multipart/form-data" method="post" class="visually-hidden">
                             @csrf
+                            <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                             <input required type="file" id="upl-photo-after" onchange="$(this).parent().submit()" multiple
                                    name="photo[]" class="form-control form-control-sm" accept=".jpg,.jpeg,.png,.webp">
                         </form>
@@ -674,6 +682,7 @@
                                       method="POST" id="photo-{{ $photo->id }}" class="visually-hidden">
                                     @csrf
                                     @method('DELETE')
+                                    <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                                 </form>
                             </div>
                         @endforeach
@@ -873,7 +882,7 @@
                 {
                     '_token': '{{ csrf_token() }}',
                     order_id: '{{ $reclamation->order_id }}',
-                    previous_url: '{{ $previous_url ?? '' }}',
+                    nav: '{{ $nav ?? '' }}',
                     user_id: $('#user_id').val(),
                     status_id: $('#status_id').val(),
                     create_date: $('#create_date').val(),

+ 4 - 3
resources/views/responsibles/edit.blade.php

@@ -4,10 +4,11 @@
 
     <div class="px-3">
 
-        <form class="row" action="{{ route('responsible.update', $responsible) }}" method="post">
+        <form class="row" action="{{ route('responsible.update', ['responsible' => $responsible, 'nav' => $nav ?? null]) }}" method="post">
             <div class="col-xxl-6">
                 <h4>Ответственный</h4>
                 @csrf
+                <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
 
                 <div class="row mb-2">
                     <label for="area_search" class="col-form-label small col-md-4 text-md-end">
@@ -37,12 +38,12 @@
                 @include('partials.input', ['name' => 'phone', 'title' => 'Телефон', 'required' => true, 'value' => $responsible->phone])
                 @include('partials.input', ['name' => 'post', 'title' => 'Должность', 'value' => $responsible->post])
 
-                @include('partials.submit', ['name' => 'Сохранить', 'delete' => ['form_id' => 'destroy', 'title' => 'Удалить']])
+                @include('partials.submit', ['name' => 'Сохранить', 'delete' => ['form_id' => 'destroy', 'title' => 'Удалить'], 'back_url' => $back_url ?? route('responsible.index', session('gp_responsibles', []))])
             </div>
         </form>
     </div>
     <div class="visually-hidden d-none">
-        <form action="{{ route('responsible.destroy', $responsible) }}" id="destroy" method="post">
+        <form action="{{ route('responsible.destroy', ['responsible' => $responsible, 'nav' => $nav ?? null]) }}" id="destroy" method="post">
             @csrf
             @method('DELETE')
         </form>

+ 3 - 1
resources/views/responsibles/index.blade.php

@@ -19,6 +19,7 @@
         'strings'   => $responsibles,
         'routeName' => 'responsible.show',
         'routeParam' => 'responsible',
+        'nav' => $nav ?? null,
     ])
 
     @include('partials.pagination', ['items' => $responsibles])
@@ -33,6 +34,7 @@
                 <div class="modal-body">
                     <form action="{{ route('responsible.store') }}" method="post">
                         @csrf
+                        <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
 
                         <div class="row mb-2">
                             <label for="area_search" class="col-form-label small col-md-4 text-md-end">
@@ -57,7 +59,7 @@
                         @include('partials.input', ['name' => 'phone', 'title' => 'Телефон', 'required' => true])
                         @include('partials.input', ['name' => 'post', 'title' => 'Должность'])
 
-                        @include('partials.submit', ['name' => 'Добавить'])
+                        @include('partials.submit', ['name' => 'Добавить', 'back_url' => route('responsible.index', session('gp_responsibles', []))])
 
                     </form>
                 </div>

+ 8 - 8
resources/views/spare_part_orders/edit.blade.php

@@ -13,7 +13,7 @@
                     @method('PUT')
                 @endif
 
-                <input type="hidden" name="previous_url" value="{{ $previous_url ?? url()->previous() }}">
+                <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
 
                 @if($spare_part_order && $spare_part_order->sparePart && $spare_part_order->sparePart->image)
                     <div class="mb-3">
@@ -98,7 +98,7 @@
 
                 <div class="mb-3 d-flex flex-wrap gap-1 align-items-start">
                     <button type="submit" class="btn btn-sm btn-success">Сохранить</button>
-                    <a href="{{ $previous_url ?? route('spare_part_orders.index') }}" class="btn btn-sm btn-secondary">Назад</a>
+                    <a href="{{ $back_url ?? route('spare_part_orders.index') }}" class="btn btn-sm btn-secondary">Назад</a>
 
                     @if($spare_part_order && $spare_part_order->status === 'ordered' && hasRole('admin,manager'))
                         <button type="submit" class="btn btn-sm btn-info" form="set-in-stock-form">Поступило на склад</button>
@@ -134,7 +134,7 @@
                                         <td>{{ $reservation->reserved_qty }}</td>
                                         <td>
                                             @if($reservation->reclamation)
-                                                <a href="{{ route('reclamations.show', $reservation->reclamation->id) }}">#{{ $reservation->reclamation->id }}</a>
+                                                <a href="{{ route('reclamations.show', ['reclamation' => $reservation->reclamation->id, 'nav' => $nav ?? null]) }}">#{{ $reservation->reclamation->id }}</a>
                                             @else
                                                 -
                                             @endif
@@ -198,7 +198,7 @@
                                         <td>
                                             {{ $movement->note }}
                                             @if($movement->source_type === 'reclamation' && $movement->source_id)
-                                                <br><small><a href="{{ route('reclamations.show', $movement->source_id) }}">Рекламация #{{ $movement->source_id }}</a></small>
+                                                <br><small><a href="{{ route('reclamations.show', ['reclamation' => $movement->source_id, 'nav' => $nav ?? null]) }}">Рекламация #{{ $movement->source_id }}</a></small>
                                             @endif
                                         </td>
                                         <td>{{ $movement->user->name ?? '-' }}</td>
@@ -216,7 +216,7 @@
 </div>
 
 @if($spare_part_order && $spare_part_order->status === 'ordered' && hasRole('admin,manager'))
-    <form id="set-in-stock-form" action="{{ route('spare_part_orders.set_in_stock', $spare_part_order) }}" method="POST" class="d-none">
+    <form id="set-in-stock-form" action="{{ route('spare_part_orders.set_in_stock', ['sparePartOrder' => $spare_part_order, 'nav' => $nav ?? null]) }}" method="POST" class="d-none">
         @csrf
     </form>
 @endif
@@ -226,7 +226,7 @@
     <div class="modal fade" id="shipModal" tabindex="-1" aria-labelledby="shipModalLabel" aria-hidden="true">
         <div class="modal-dialog">
             <div class="modal-content">
-                <form action="{{ route('spare_part_orders.ship', $spare_part_order) }}" method="POST">
+                <form action="{{ route('spare_part_orders.ship', ['sparePartOrder' => $spare_part_order, 'nav' => $nav ?? null]) }}" method="POST">
                     @csrf
                     <div class="modal-header">
                         <h1 class="modal-title fs-5" id="shipModalLabel">Отгрузка</h1>
@@ -267,7 +267,7 @@
     <div class="modal fade" id="correctModal" tabindex="-1" aria-labelledby="correctModalLabel" aria-hidden="true">
         <div class="modal-dialog">
             <div class="modal-content">
-                <form action="{{ route('spare_part_orders.correct', $spare_part_order) }}" method="POST">
+                <form action="{{ route('spare_part_orders.correct', ['sparePartOrder' => $spare_part_order, 'nav' => $nav ?? null]) }}" method="POST">
                     @csrf
                     <div class="modal-header">
                         <h1 class="modal-title fs-5" id="correctModalLabel">Коррекция остатка</h1>
@@ -316,7 +316,7 @@
                     @endif
                 </div>
                 <div class="modal-footer">
-                    <form action="{{ route('spare_part_orders.destroy', $spare_part_order) }}" method="POST">
+                    <form action="{{ route('spare_part_orders.destroy', ['sparePartOrder' => $spare_part_order, 'nav' => $nav ?? null]) }}" method="POST">
                         @csrf
                         @method('DELETE')
                         <button type="submit" class="btn btn-danger">Удалить</button>

+ 9 - 9
resources/views/spare_parts/edit.blade.php

@@ -12,7 +12,7 @@
                     @if($spare_part && hasRole('admin'))
                         <button class="btn btn-sm text-success" onclick="$('#upl-image').trigger('click');"><i class="bi bi-plus-circle-fill"></i> Загрузить изображение</button>
 
-                        <form action="{{ route('spare_parts.upload_image', ['sparePart' => $spare_part]) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
+                        <form action="{{ route('spare_parts.upload_image', ['sparePart' => $spare_part, 'nav' => $nav ?? null]) }}" class="visually-hidden" method="POST" enctype="multipart/form-data">
                             @csrf
                             <input type="file" name="image" onchange="$(this).parent().submit()" required id="upl-image" accept="image/*" />
                         </form>
@@ -27,7 +27,7 @@
                     @method('PUT')
                 @endif
 
-                <input type="hidden" name="previous_url" value="{{ $previous_url ?? url()->previous() }}">
+                <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
 
                 <div class="row">
                     {{-- Левая колонка --}}
@@ -218,7 +218,7 @@
                         @if(hasRole('admin,manager'))
                             <button type="submit" class="btn btn-sm btn-success">Сохранить</button>
                         @endif
-                        <a href="{{ $previous_url ?? route('spare_parts.index') }}" class="btn btn-sm btn-secondary">Назад</a>
+                        <a href="{{ $back_url ?? route('spare_parts.index') }}" class="btn btn-sm btn-secondary">Назад</a>
 
                         @if($spare_part && hasRole('admin'))
                             <button type="button" class="btn btn-sm btn-danger float-end" data-bs-toggle="modal" data-bs-target="#deleteModal">
@@ -311,7 +311,7 @@
                                             <tr>
                                                 <td>
                                                     @if($reservation->reclamation)
-                                                        <a href="{{ route('reclamations.show', $reservation->reclamation->id) }}">
+                                                        <a href="{{ route('reclamations.show', ['reclamation' => $reservation->reclamation->id, 'nav' => $nav ?? null]) }}">
                                                             #{{ $reservation->reclamation->id }}
                                                         </a>
                                                     @else
@@ -320,7 +320,7 @@
                                                 </td>
                                                 <td>
                                                     @if($reservation->sparePartOrder)
-                                                        <a href="{{ route('spare_part_orders.show', $reservation->sparePartOrder->id) }}">
+                                                        <a href="{{ route('spare_part_orders.show', ['sparePartOrder' => $reservation->sparePartOrder->id, 'nav' => $nav ?? null]) }}">
                                                             #{{ $reservation->sparePartOrder->id }}
                                                         </a>
                                                     @else
@@ -374,7 +374,7 @@
                                             <tr>
                                                 <td>
                                                     @if($shortage->reclamation)
-                                                        <a href="{{ route('reclamations.show', $shortage->reclamation->id) }}">
+                                                        <a href="{{ route('reclamations.show', ['reclamation' => $shortage->reclamation->id, 'nav' => $nav ?? null]) }}">
                                                             #{{ $shortage->reclamation->id }}
                                                         </a>
                                                     @else
@@ -428,7 +428,7 @@
                                         @foreach($ordersInStock as $order)
                                             <tr>
                                                 <td>
-                                                    <a href="{{ route('spare_part_orders.show', $order->id) }}">#{{ $order->id }}</a>
+                                                    <a href="{{ route('spare_part_orders.show', ['sparePartOrder' => $order->id, 'nav' => $nav ?? null]) }}">#{{ $order->id }}</a>
                                                 </td>
                                                 <td>{{ $order->display_order_number }}</td>
                                                 <td class="text-center">{{ $order->available_qty }}</td>
@@ -481,7 +481,7 @@
                                                 <td>{{ $movement->type_name }}</td>
                                                 <td>
                                                     @if($movement->sparePartOrder)
-                                                        <a href="{{ route('spare_part_orders.show', $movement->sparePartOrder->id) }}">{{ $movement->sparePartOrder->display_order_number }}</a>
+                                                        <a href="{{ route('spare_part_orders.show', ['sparePartOrder' => $movement->sparePartOrder->id, 'nav' => $nav ?? null]) }}">{{ $movement->sparePartOrder->display_order_number }}</a>
                                                     @else
                                                         -
                                                     @endif
@@ -497,7 +497,7 @@
                                                 <td>
                                                     {{ $movement->note }}
                                                     @if($movement->source_type === 'reclamation' && $movement->source_id)
-                                                        <br><small><a href="{{ route('reclamations.show', $movement->source_id) }}">Рекламация #{{ $movement->source_id }}</a></small>
+                                                        <br><small><a href="{{ route('reclamations.show', ['reclamation' => $movement->source_id, 'nav' => $nav ?? null]) }}">Рекламация #{{ $movement->source_id }}</a></small>
                                                     @endif
                                                 </td>
                                                 <td>{{ $movement->user->name ?? '-' }}</td>

+ 5 - 4
resources/views/users/edit.blade.php

@@ -3,8 +3,9 @@
 @section('content')
     <div class="px-3">
         <div class="col-xxl-6 offset-xxl-2">
-            <form action="{{ route('user.store') }}" method="post">
+            <form action="{{ route('user.store', ['nav' => $nav ?? null]) }}" method="post">
                 @csrf
+                <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
 
                 @if($user)
                     <input type="hidden" name="id" value="{{ $user->id }}">
@@ -68,7 +69,7 @@
                         <a href="#" class="btn btn-sm btn-warning undelete">Восстановить</a>
                     </div>
                 @else
-                    @include('partials.submit', ['delete' => ['form_id' => 'delete-user']])
+                    @include('partials.submit', ['delete' => ['form_id' => 'delete-user'], 'back_url' => $back_url ?? route('user.index', session('gp_users', []))])
                     @if($user && auth()->id() !== $user->id)
                         <div class="text-center mt-2">
                             <a href="#" class="btn btn-sm btn-outline-primary impersonate-user">Impersonate</a>
@@ -77,10 +78,10 @@
                 @endif
             </form>
             @if($user)
-                <form action="{{ route('user.undelete', $user->id) }}" method="post" class="d-none" id="undelete-user">
+                <form action="{{ route('user.undelete', ['user' => $user->id, 'nav' => $nav ?? null]) }}" method="post" class="d-none" id="undelete-user">
                     @csrf
                 </form>
-                <form action="{{ route('user.destroy', $user->id) }}" method="post" class="d-none" id="delete-user">
+                <form action="{{ route('user.destroy', ['user' => $user->id, 'nav' => $nav ?? null]) }}" method="post" class="d-none" id="delete-user">
                     @method('DELETE')
                     @csrf
                 </form>

+ 3 - 1
resources/views/users/index.blade.php

@@ -18,6 +18,7 @@
         'strings'   => $users,
         'routeName' => 'user.show',
         'routeParam' => 'user',
+        'nav' => $nav ?? null,
     ])
 
     @include('partials.pagination', ['items' => $users])
@@ -33,6 +34,7 @@
                 <div class="modal-body">
                     <form action="{{ route('user.store') }}" method="post">
                         @csrf
+                        <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
 
                         @include('partials.input', ['name' => 'email', 'type' => 'text', 'title' => 'Логин/email', 'required' => true])
                         @include('partials.input', ['name' => 'name', 'title' => 'Имя', 'required' => true])
@@ -40,7 +42,7 @@
                         @include('partials.input', ['name' => 'password', 'type' => 'password', 'title' => 'Пароль', 'required' => true])
                         @include('partials.select', ['name' => 'role', 'title' => 'Роль', 'options' => getRoles(), 'value' => $user->role ?? \App\Models\Role::MANAGER])
 
-                        @include('partials.submit', ['name' => 'Добавить'])
+                        @include('partials.submit', ['name' => 'Добавить', 'back_url' => route('user.index', session('gp_users', []))])
 
                     </form>
                 </div>

+ 22 - 0
tests/Feature/AdminAreaControllerTest.php

@@ -107,6 +107,28 @@ class AdminAreaControllerTest extends TestCase
         $response->assertSee($area->name);
     }
 
+    public function test_area_show_uses_nav_context_back_url(): void
+    {
+        $area = Area::factory()->create(['district_id' => $this->district->id]);
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    'area-nav' => [
+                        'updated_at' => now()->timestamp,
+                        'stack' => [
+                            route('admin.area.index', ['page' => 2]),
+                        ],
+                    ],
+                ],
+            ])
+            ->get(route('admin.area.show', ['area' => $area->id, 'nav' => 'area-nav']));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', 'area-nav');
+        $response->assertViewHas('back_url', route('admin.area.index', ['page' => 2]));
+    }
+
     public function test_manager_cannot_view_area_edit_form(): void
     {
         $area = Area::factory()->create(['district_id' => $this->district->id]);

+ 22 - 0
tests/Feature/AdminDistrictControllerTest.php

@@ -93,6 +93,28 @@ class AdminDistrictControllerTest extends TestCase
         $response->assertSee($district->name);
     }
 
+    public function test_district_show_uses_nav_context_back_url(): void
+    {
+        $district = District::factory()->create();
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    'district-nav' => [
+                        'updated_at' => now()->timestamp,
+                        'stack' => [
+                            route('admin.district.index', ['page' => 2]),
+                        ],
+                    ],
+                ],
+            ])
+            ->get(route('admin.district.show', ['district' => $district->id, 'nav' => 'district-nav']));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', 'district-nav');
+        $response->assertViewHas('back_url', route('admin.district.index', ['page' => 2]));
+    }
+
     public function test_manager_cannot_view_district_edit_form(): void
     {
         $district = District::factory()->create();

+ 22 - 0
tests/Feature/ContractControllerTest.php

@@ -108,6 +108,28 @@ class ContractControllerTest extends TestCase
         $response->assertViewIs('contracts.edit');
     }
 
+    public function test_contract_show_uses_nav_context_back_url(): void
+    {
+        $contract = Contract::factory()->create();
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    'contract-nav' => [
+                        'updated_at' => now()->timestamp,
+                        'stack' => [
+                            route('contract.index', ['page' => 2]),
+                        ],
+                    ],
+                ],
+            ])
+            ->get(route('contract.show', ['contract' => $contract, 'nav' => 'contract-nav']));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', 'contract-nav');
+        $response->assertViewHas('back_url', route('contract.index', ['page' => 2]));
+    }
+
     // ==================== Store ====================
 
     public function test_admin_can_create_contract(): void

+ 31 - 0
tests/Feature/ImportControllerTest.php

@@ -88,6 +88,37 @@ class ImportControllerTest extends TestCase
         $response->assertViewIs('import.show');
     }
 
+    public function test_import_show_uses_nav_context_back_url(): void
+    {
+        $import = Import::factory()->create();
+
+        $indexResponse = $this->actingAs($this->adminUser)
+            ->get(route('import.index', [
+                'filters' => ['type' => 'orders'],
+            ]));
+
+        $nav = $indexResponse->viewData('nav');
+
+        $response = $this->actingAs($this->adminUser)
+            ->get(route('import.show', [
+                'import' => $import,
+                'nav' => $nav,
+            ]));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', $nav);
+        $response->assertViewHas('back_url', function (string $backUrl): bool {
+            if (!str_starts_with($backUrl, route('import.index'))) {
+                return false;
+            }
+
+            $query = parse_url($backUrl, PHP_URL_QUERY);
+            parse_str((string) $query, $params);
+
+            return ($params['filters']['type'] ?? null) === 'orders';
+        });
+    }
+
     // ==================== Store (upload file + dispatch job) ====================
 
     public function test_admin_can_upload_valid_xlsx_and_dispatches_job(): void

+ 41 - 1
tests/Feature/MafOrderControllerTest.php

@@ -28,6 +28,15 @@ class MafOrderControllerTest extends TestCase
         $this->assistantHeadUser = User::factory()->create(['role' => Role::ASSISTANT_HEAD]);
     }
 
+    private function assertRedirectsToMafShowWithGeneratedNav($response, MafOrder $mafOrder): void
+    {
+        $location = $response->headers->get('Location');
+
+        $this->assertNotNull($location);
+        $this->assertStringStartsWith(route('maf_order.show', $mafOrder), $location);
+        $this->assertStringContainsString('nav=', $location);
+    }
+
     // ==================== Authentication ====================
 
     public function test_guest_cannot_access_maf_orders_index(): void
@@ -144,6 +153,37 @@ class MafOrderControllerTest extends TestCase
         $response->assertViewIs('maf_orders.edit');
     }
 
+    public function test_maf_order_show_uses_nav_context_back_url(): void
+    {
+        $mafOrder = MafOrder::factory()->create();
+
+        $indexResponse = $this->actingAs($this->adminUser)
+            ->get(route('maf_order.index', [
+                'filters' => ['user_name' => $this->adminUser->name],
+            ]));
+
+        $nav = $indexResponse->viewData('nav');
+
+        $response = $this->actingAs($this->adminUser)
+            ->get(route('maf_order.show', [
+                'maf_order' => $mafOrder,
+                'nav' => $nav,
+            ]));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', $nav);
+        $response->assertViewHas('back_url', function (string $backUrl): bool {
+            if (!str_starts_with($backUrl, route('maf_order.index'))) {
+                return false;
+            }
+
+            $query = parse_url($backUrl, PHP_URL_QUERY);
+            parse_str((string) $query, $params);
+
+            return ($params['filters']['user_name'] ?? null) === $this->adminUser->name;
+        });
+    }
+
     public function test_guest_cannot_view_maf_order_details(): void
     {
         $mafOrder = MafOrder::factory()->create();
@@ -226,7 +266,7 @@ class MafOrderControllerTest extends TestCase
         $response = $this->actingAs($this->adminUser)
             ->post(route('maf_order.set_in_stock', $mafOrder));
 
-        $response->assertRedirect(route('maf_order.show', $mafOrder));
+        $this->assertRedirectsToMafShowWithGeneratedNav($response, $mafOrder);
         $this->assertDatabaseHas('maf_orders', [
             'id'       => $mafOrder->id,
             'in_stock' => 8,

+ 67 - 0
tests/Feature/OrderControllerTest.php

@@ -209,6 +209,73 @@ class OrderControllerTest extends TestCase
         ]);
     }
 
+    public function test_order_show_uses_nav_context_for_back_url(): void
+    {
+        $order = Order::factory()->create();
+
+        $indexResponse = $this->actingAs($this->managerUser)
+            ->get(route('order.index', [
+                'filters' => ['object_address' => 'Тестовый адрес'],
+            ]));
+
+        $nav = $indexResponse->viewData('nav');
+
+        $response = $this->actingAs($this->managerUser)
+            ->get(route('order.show', [
+                'order' => $order,
+                'nav' => $nav,
+            ]));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', $nav);
+        $response->assertViewHas('back_url', function (string $backUrl): bool {
+            if (!str_starts_with($backUrl, route('order.index'))) {
+                return false;
+            }
+
+            $query = parse_url($backUrl, PHP_URL_QUERY);
+            parse_str((string) $query, $params);
+
+            return ($params['filters']['object_address'] ?? null) === 'Тестовый адрес';
+        });
+    }
+
+    public function test_order_store_redirects_to_show_with_nav_token(): void
+    {
+        $district = District::factory()->create();
+        $area = Area::factory()->create();
+        $objectType = ObjectType::factory()->create();
+
+        $response = $this->actingAs($this->managerUser)
+            ->withSession([
+                'navigation' => [
+                    'order-nav-token' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('order.index'),
+                        ],
+                    ],
+                ],
+            ])
+            ->post(route('order.store'), [
+                'nav' => 'order-nav-token',
+                'name' => 'Тестовый заказ',
+                'user_id' => $this->managerUser->id,
+                'district_id' => $district->id,
+                'area_id' => $area->id,
+                'object_address' => 'ул. Навигационная, д. 5',
+                'object_type_id' => $objectType->id,
+                'comment' => 'Тестовый комментарий',
+            ]);
+
+        $createdOrder = Order::query()->where('object_address', 'ул. Навигационная, д. 5')->firstOrFail();
+
+        $response->assertRedirect(route('order.show', [
+            'order' => $createdOrder,
+            'nav' => 'order-nav-token',
+        ]));
+    }
+
     public function test_creating_order_sets_tg_group_name(): void
     {
         $district = District::factory()->create(['shortname' => 'ЦАО']);

+ 60 - 0
tests/Feature/ProductControllerTest.php

@@ -142,6 +142,36 @@ class ProductControllerTest extends TestCase
         $response->assertViewIs('catalog.edit');
     }
 
+    public function test_product_show_uses_nav_context_back_url(): void
+    {
+        $product = Product::factory()->create();
+        $reclamation = \App\Models\Reclamation::factory()->create();
+
+        $indexResponse = $this->actingAs($this->adminUser)
+            ->get(route('reclamations.index'));
+
+        $nav = $indexResponse->viewData('nav');
+
+        $this->actingAs($this->adminUser)
+            ->get(route('reclamations.show', [
+                'reclamation' => $reclamation,
+                'nav' => $nav,
+            ]))
+            ->assertOk();
+
+        $response = $this->actingAs($this->adminUser)
+            ->get(route('catalog.show', [
+                'product' => $product,
+                'nav' => $nav,
+            ]));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', $nav);
+        $response->assertViewHas('back_url', route('reclamations.show', [
+            'reclamation' => $reclamation,
+        ]));
+    }
+
     // --- Store (3) ---
 
     public function test_admin_can_create_product(): void
@@ -207,6 +237,36 @@ class ProductControllerTest extends TestCase
         $response->assertForbidden();
     }
 
+    public function test_update_product_redirects_to_parent_url_from_nav_context(): void
+    {
+        $product = Product::factory()->create();
+        $reclamation = \App\Models\Reclamation::factory()->create();
+        $nav = 'catalog-nav-token';
+
+        $data = $this->validProductData();
+        $data['article'] = 'UPDATED-NAV';
+        $data['nav'] = $nav;
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    $nav => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('reclamations.index'),
+                            route('reclamations.show', $reclamation),
+                            route('catalog.show', $product),
+                        ],
+                    ],
+                ],
+            ])
+            ->post(route('catalog.update', $product), $data);
+
+        $response->assertRedirect(route('reclamations.show', [
+            'reclamation' => $reclamation,
+        ]));
+    }
+
     // --- Delete (1) ---
 
     public function test_admin_can_delete_product(): void

+ 67 - 0
tests/Feature/ProductSKUControllerTest.php

@@ -211,6 +211,35 @@ class ProductSKUControllerTest extends TestCase
         $response->assertViewIs('products_sku.edit');
     }
 
+    public function test_product_sku_show_uses_nav_context_back_url(): void
+    {
+        $sku = ProductSKU::factory()->create();
+        $reclamation = \App\Models\Reclamation::factory()->create();
+
+        $indexResponse = $this->actingAs($this->adminUser)
+            ->get(route('reclamations.index'));
+        $nav = $indexResponse->viewData('nav');
+
+        $this->actingAs($this->adminUser)
+            ->get(route('reclamations.show', [
+                'reclamation' => $reclamation,
+                'nav' => $nav,
+            ]))
+            ->assertOk();
+
+        $response = $this->actingAs($this->adminUser)
+            ->get(route('product_sku.show', [
+                'product_sku' => $sku,
+                'nav' => $nav,
+            ]));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', $nav);
+        $response->assertViewHas('back_url', route('reclamations.show', [
+            'reclamation' => $reclamation,
+        ]));
+    }
+
     // ==================== Update ====================
 
     public function test_admin_can_update_product_sku(): void
@@ -259,6 +288,44 @@ class ProductSKUControllerTest extends TestCase
         $response->assertRedirect();
     }
 
+    public function test_update_product_sku_redirects_to_parent_url_from_nav_context(): void
+    {
+        $product = Product::factory()->create();
+        $order = Order::factory()->create();
+        $sku = ProductSKU::factory()->create([
+            'product_id' => $product->id,
+            'order_id' => $order->id,
+        ]);
+        $reclamation = \App\Models\Reclamation::factory()->create();
+        $nav = 'product-sku-nav-token';
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    $nav => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('reclamations.index'),
+                            route('reclamations.show', $reclamation),
+                            route('product_sku.show', $sku),
+                        ],
+                    ],
+                ],
+            ])
+            ->post(route('product_sku.update', $sku), [
+                'nav' => $nav,
+                'product_id' => $product->id,
+                'order_id' => $order->id,
+                'status' => 'shipped',
+                'factory_number' => 'FN-NAV-123',
+                'comment' => 'Updated through nav',
+            ]);
+
+        $response->assertRedirect(route('reclamations.show', [
+            'reclamation' => $reclamation,
+        ]));
+    }
+
     // ==================== Export ====================
 
     public function test_admin_can_export_mafs(): void

+ 94 - 1
tests/Feature/ReclamationControllerTest.php

@@ -156,6 +156,37 @@ class ReclamationControllerTest extends TestCase
         $response->assertViewIs('reclamations.edit');
     }
 
+    public function test_reclamation_show_uses_nav_context_for_back_url(): void
+    {
+        $reclamation = Reclamation::factory()->create();
+
+        $indexResponse = $this->actingAs($this->managerUser)
+            ->get(route('reclamations.index', [
+                'filters' => ['comment' => 'КС готова'],
+            ]));
+
+        $nav = $indexResponse->viewData('nav');
+
+        $response = $this->actingAs($this->managerUser)
+            ->get(route('reclamations.show', [
+                'reclamation' => $reclamation,
+                'nav' => $nav,
+            ]));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', $nav);
+        $response->assertViewHas('back_url', function (string $backUrl): bool {
+            if (!str_starts_with($backUrl, route('reclamations.index'))) {
+                return false;
+            }
+
+            $query = parse_url($backUrl, PHP_URL_QUERY);
+            parse_str((string) $query, $params);
+
+            return ($params['filters']['comment'] ?? null) === 'КС готова';
+        });
+    }
+
     public function test_reclamation_details_show_spare_part_note_in_input_instead_of_used_in_maf(): void
     {
         $sparePart = \App\Models\SparePart::factory()->create([
@@ -213,7 +244,10 @@ class ReclamationControllerTest extends TestCase
                 'whats_done' => 'Что сделано',
             ]);
 
-        $response->assertRedirect(route('reclamations.show', $reclamation));
+        $location = $response->headers->get('Location');
+        $this->assertNotNull($location);
+        $this->assertStringContainsString('/reclamations/show/' . $reclamation->id, $location);
+        $this->assertStringContainsString('nav=', $location);
 
         $this->assertDatabaseHas('reclamations', [
             'id' => $reclamation->id,
@@ -221,6 +255,65 @@ class ReclamationControllerTest extends TestCase
         ]);
     }
 
+    public function test_update_redirects_with_nav_token(): void
+    {
+        $reclamation = Reclamation::factory()->create([
+            'reason' => 'Старая причина',
+        ]);
+        $nav = 'nav-test-token';
+
+        $response = $this->actingAs($this->managerUser)
+            ->withSession([
+                'navigation' => [
+                    $nav => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('reclamations.index'),
+                            route('reclamations.show', $reclamation),
+                        ],
+                    ],
+                ],
+            ])
+            ->post(route('reclamations.update', $reclamation), [
+                'nav' => $nav,
+                'user_id' => $reclamation->user_id,
+                'status_id' => $reclamation->status_id,
+                'create_date' => $reclamation->create_date,
+                'finish_date' => $reclamation->finish_date,
+                'reason' => 'Новая причина',
+                'guarantee' => 'Гарантия',
+                'whats_done' => 'Что сделано',
+            ]);
+
+        $response->assertRedirect(route('reclamations.show', [
+            'reclamation' => $reclamation,
+            'nav' => $nav,
+        ]));
+    }
+
+    public function test_ajax_update_returns_no_content_without_redirect_location(): void
+    {
+        $reclamation = Reclamation::factory()->create([
+            'reason' => 'Старая причина',
+        ]);
+
+        $response = $this->actingAs($this->managerUser)
+            ->withHeader('X-Requested-With', 'XMLHttpRequest')
+            ->post(route('reclamations.update', $reclamation), [
+                'nav' => 'ajax-nav-token',
+                'user_id' => $reclamation->user_id,
+                'status_id' => $reclamation->status_id,
+                'create_date' => $reclamation->create_date,
+                'finish_date' => $reclamation->finish_date,
+                'reason' => 'Новая причина',
+                'guarantee' => 'Гарантия',
+                'whats_done' => 'Что сделано',
+            ]);
+
+        $response->assertNoContent();
+        $this->assertNull($response->headers->get('Location'));
+    }
+
     // ==================== Delete ====================
 
     public function test_can_delete_reclamation(): void

+ 1 - 3
tests/Feature/ReportControllerTest.php

@@ -177,9 +177,7 @@ class ReportControllerTest extends TestCase
         $response->assertViewHas('totalHandedOverSum', $expectedSum);
         $response->assertViewHas('totalDoneSum', $expectedSum);
         $response->assertViewHas('byDistrict', function (array $byDistrict) use ($handedOverOrder, $expectedSum) {
-            $districtName = $handedOverOrder->district->shortname;
-
-            return ($byDistrict[$districtName]['doneSum'] ?? null) === $expectedSum;
+            return ($byDistrict[$handedOverOrder->district_id]['doneSum'] ?? null) === $expectedSum;
         });
     }
 }

+ 26 - 0
tests/Feature/ResponsibleControllerTest.php

@@ -83,4 +83,30 @@ class ResponsibleControllerTest extends TestCase
         $response->assertOk();
         $response->assertJsonFragment(['Тверской']);
     }
+
+    public function test_responsible_show_uses_nav_context_back_url(): void
+    {
+        $responsible = Responsible::query()->create([
+            'name' => 'Иван Иванов',
+            'phone' => '+79990000001',
+            'post' => 'Куратор',
+        ]);
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    'responsible-nav' => [
+                        'updated_at' => now()->timestamp,
+                        'stack' => [
+                            route('responsible.index', ['page' => 3]),
+                        ],
+                    ],
+                ],
+            ])
+            ->get(route('responsible.show', ['responsible' => $responsible, 'nav' => 'responsible-nav']));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', 'responsible-nav');
+        $response->assertViewHas('back_url', route('responsible.index', ['page' => 3]));
+    }
 }

+ 80 - 0
tests/Feature/SparePartControllerTest.php

@@ -77,6 +77,37 @@ class SparePartControllerTest extends TestCase
         $response->assertViewIs('spare_parts.edit');
     }
 
+    public function test_spare_part_show_uses_nav_context_back_url(): void
+    {
+        $sparePart = SparePart::factory()->create();
+
+        $indexResponse = $this->actingAs($this->adminUser)
+            ->get(route('spare_parts.index', [
+                'filters' => ['used_in_maf' => 'МАФ-42'],
+            ]));
+
+        $nav = $indexResponse->viewData('nav');
+
+        $response = $this->actingAs($this->adminUser)
+            ->get(route('spare_parts.show', [
+                'sparePart' => $sparePart,
+                'nav' => $nav,
+            ]));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', $nav);
+        $response->assertViewHas('back_url', function (string $backUrl): bool {
+            if (!str_starts_with($backUrl, route('spare_parts.index'))) {
+                return false;
+            }
+
+            $query = parse_url($backUrl, PHP_URL_QUERY);
+            parse_str((string) $query, $params);
+
+            return ($params['filters']['used_in_maf'] ?? null) === 'МАФ-42';
+        });
+    }
+
     public function test_guest_cannot_view_spare_part(): void
     {
         $sparePart = SparePart::factory()->create();
@@ -106,6 +137,30 @@ class SparePartControllerTest extends TestCase
         ]);
     }
 
+    public function test_store_spare_part_redirects_to_parent_url_from_nav_context(): void
+    {
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    'spare-part-nav-token' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('spare_parts.index'),
+                            route('spare_parts.create'),
+                        ],
+                    ],
+                ],
+            ])
+            ->post(route('spare_parts.store'), [
+                'nav' => 'spare-part-nav-token',
+                'article' => 'SP-NAV-001',
+                'used_in_maf' => 'MAF-200',
+                'customer_price' => 250.00,
+            ]);
+
+        $response->assertRedirect(route('spare_parts.index'));
+    }
+
     public function test_store_requires_article(): void
     {
         $response = $this->actingAs($this->adminUser)
@@ -157,6 +212,31 @@ class SparePartControllerTest extends TestCase
         ]);
     }
 
+    public function test_update_spare_part_redirects_to_parent_url_from_nav_context(): void
+    {
+        $sparePart = SparePart::factory()->create(['article' => 'SP-OLD']);
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    'spare-part-update-token' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('spare_parts.index'),
+                            route('spare_parts.show', $sparePart),
+                        ],
+                    ],
+                ],
+            ])
+            ->put(route('spare_parts.update', $sparePart), [
+                'nav' => 'spare-part-update-token',
+                'article' => 'SP-UPDATED-NAV',
+                'min_stock' => 9,
+            ]);
+
+        $response->assertRedirect(route('spare_parts.index'));
+    }
+
     public function test_manager_cannot_update_spare_part(): void
     {
         $sparePart = SparePart::factory()->create();

+ 139 - 12
tests/Feature/SparePartOrderControllerTest.php

@@ -27,6 +27,15 @@ class SparePartOrderControllerTest extends TestCase
         $this->managerUser = User::factory()->create(['role' => Role::MANAGER]);
     }
 
+    private function assertRedirectsToShowWithGeneratedNav($response, SparePartOrder $sparePartOrder): void
+    {
+        $location = $response->headers->get('Location');
+
+        $this->assertNotNull($location);
+        $this->assertStringStartsWith(route('spare_part_orders.show', $sparePartOrder), $location);
+        $this->assertStringContainsString('nav=', $location);
+    }
+
     // ==================== Authentication ====================
 
     public function test_guest_cannot_access_spare_part_orders_index(): void
@@ -124,6 +133,37 @@ class SparePartOrderControllerTest extends TestCase
         $response->assertViewIs('spare_part_orders.edit');
     }
 
+    public function test_spare_part_order_show_uses_nav_context_back_url(): void
+    {
+        $sparePartOrder = SparePartOrder::factory()->create();
+
+        $indexResponse = $this->actingAs($this->adminUser)
+            ->get(route('spare_part_orders.index', [
+                'spare_part_id' => $sparePartOrder->spare_part_id,
+            ]));
+
+        $nav = $indexResponse->viewData('nav');
+
+        $response = $this->actingAs($this->adminUser)
+            ->get(route('spare_part_orders.show', [
+                'sparePartOrder' => $sparePartOrder,
+                'nav' => $nav,
+            ]));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', $nav);
+        $response->assertViewHas('back_url', function (string $backUrl) use ($sparePartOrder): bool {
+            if (!str_starts_with($backUrl, route('spare_part_orders.index'))) {
+                return false;
+            }
+
+            $query = parse_url($backUrl, PHP_URL_QUERY);
+            parse_str((string) $query, $params);
+
+            return (int) ($params['spare_part_id'] ?? 0) === (int) $sparePartOrder->spare_part_id;
+        });
+    }
+
     public function test_ordered_spare_part_order_page_renders_set_in_stock_button_with_separate_form(): void
     {
         $sparePartOrder = SparePartOrder::factory()->ordered()->create();
@@ -189,6 +229,33 @@ class SparePartOrderControllerTest extends TestCase
         ]);
     }
 
+    public function test_store_spare_part_order_redirects_to_parent_url_from_nav_context(): void
+    {
+        $sparePart = SparePart::factory()->create();
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    'spare-part-order-nav-token' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('spare_part_orders.index'),
+                            route('spare_part_orders.create'),
+                        ],
+                    ],
+                ],
+            ])
+            ->post(route('spare_part_orders.store'), [
+                'nav' => 'spare-part-order-nav-token',
+                'spare_part_id' => $sparePart->id,
+                'status' => SparePartOrder::STATUS_ORDERED,
+                'ordered_quantity' => 10,
+                'source_text' => 'Test Supplier',
+            ]);
+
+        $response->assertRedirect(route('spare_part_orders.index'));
+    }
+
     public function test_manager_can_create_spare_part_order(): void
     {
         $sparePart = SparePart::factory()->create();
@@ -251,6 +318,32 @@ class SparePartOrderControllerTest extends TestCase
         ]);
     }
 
+    public function test_update_spare_part_order_redirects_to_parent_url_from_nav_context(): void
+    {
+        $sparePartOrder = SparePartOrder::factory()->ordered()->create();
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    'spare-part-order-update-token' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('spare_part_orders.index'),
+                            route('spare_part_orders.show', $sparePartOrder),
+                        ],
+                    ],
+                ],
+            ])
+            ->put(route('spare_part_orders.update', $sparePartOrder), [
+                'nav' => 'spare-part-order-update-token',
+                'spare_part_id' => $sparePartOrder->spare_part_id,
+                'status' => SparePartOrder::STATUS_ORDERED,
+                'ordered_quantity' => 21,
+            ]);
+
+        $response->assertRedirect(route('spare_part_orders.index'));
+    }
+
     public function test_manager_can_update_spare_part_order(): void
     {
         $sparePartOrder = SparePartOrder::factory()->ordered()->create();
@@ -301,7 +394,7 @@ class SparePartOrderControllerTest extends TestCase
         $response = $this->actingAs($this->adminUser)
             ->delete(route('spare_part_orders.destroy', $sparePartOrder));
 
-        $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
+        $this->assertRedirectsToShowWithGeneratedNav($response, $sparePartOrder);
         $response->assertSessionHas('error');
         $this->assertDatabaseHas('spare_part_orders', ['id' => $sparePartOrder->id, 'deleted_at' => null]);
     }
@@ -332,9 +425,26 @@ class SparePartOrderControllerTest extends TestCase
         $sparePartOrder = SparePartOrder::factory()->ordered()->create();
 
         $response = $this->actingAs($this->adminUser)
-            ->post(route('spare_part_orders.set_in_stock', $sparePartOrder));
-
-        $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
+            ->withSession([
+                'navigation' => [
+                    'set-in-stock-nav-token' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('spare_part_orders.index'),
+                            route('spare_part_orders.show', $sparePartOrder),
+                        ],
+                    ],
+                ],
+            ])
+            ->post(route('spare_part_orders.set_in_stock', [
+                'sparePartOrder' => $sparePartOrder,
+                'nav' => 'set-in-stock-nav-token',
+            ]));
+
+        $response->assertRedirect(route('spare_part_orders.show', [
+            'sparePartOrder' => $sparePartOrder,
+            'nav' => 'set-in-stock-nav-token',
+        ]));
         $response->assertSessionHas('success');
         $this->assertDatabaseHas('spare_part_orders', [
             'id' => $sparePartOrder->id,
@@ -347,9 +457,26 @@ class SparePartOrderControllerTest extends TestCase
         $sparePartOrder = SparePartOrder::factory()->ordered()->create();
 
         $response = $this->actingAs($this->managerUser)
-            ->post(route('spare_part_orders.set_in_stock', $sparePartOrder));
-
-        $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
+            ->withSession([
+                'navigation' => [
+                    'set-in-stock-manager-token' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('spare_part_orders.index'),
+                            route('spare_part_orders.show', $sparePartOrder),
+                        ],
+                    ],
+                ],
+            ])
+            ->post(route('spare_part_orders.set_in_stock', [
+                'sparePartOrder' => $sparePartOrder,
+                'nav' => 'set-in-stock-manager-token',
+            ]));
+
+        $response->assertRedirect(route('spare_part_orders.show', [
+            'sparePartOrder' => $sparePartOrder,
+            'nav' => 'set-in-stock-manager-token',
+        ]));
         $response->assertSessionHas('success');
     }
 
@@ -430,7 +557,7 @@ class SparePartOrderControllerTest extends TestCase
                 'note' => 'Тестовая отгрузка',
             ]);
 
-        $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
+        $this->assertRedirectsToShowWithGeneratedNav($response, $sparePartOrder);
         $response->assertSessionHas('success');
         $this->assertDatabaseHas('spare_part_orders', [
             'id' => $sparePartOrder->id,
@@ -448,7 +575,7 @@ class SparePartOrderControllerTest extends TestCase
                 'note' => 'Попытка отгрузить больше чем есть',
             ]);
 
-        $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
+        $this->assertRedirectsToShowWithGeneratedNav($response, $sparePartOrder);
         $response->assertSessionHas('error');
         $this->assertDatabaseHas('spare_part_orders', [
             'id' => $sparePartOrder->id,
@@ -478,7 +605,7 @@ class SparePartOrderControllerTest extends TestCase
                 'note' => 'Отгрузка менеджером',
             ]);
 
-        $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
+        $this->assertRedirectsToShowWithGeneratedNav($response, $sparePartOrder);
         $response->assertSessionHas('success');
     }
 
@@ -506,7 +633,7 @@ class SparePartOrderControllerTest extends TestCase
                 'reason' => 'Инвентаризация — обнаружена недостача',
             ]);
 
-        $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
+        $this->assertRedirectsToShowWithGeneratedNav($response, $sparePartOrder);
         $response->assertSessionHas('success');
         $this->assertDatabaseHas('spare_part_orders', [
             'id' => $sparePartOrder->id,
@@ -524,7 +651,7 @@ class SparePartOrderControllerTest extends TestCase
                 'reason' => 'Без изменений',
             ]);
 
-        $response->assertRedirect(route('spare_part_orders.show', $sparePartOrder));
+        $this->assertRedirectsToShowWithGeneratedNav($response, $sparePartOrder);
         $response->assertSessionHas('error');
     }
 

+ 22 - 0
tests/Feature/UserControllerTest.php

@@ -128,6 +128,28 @@ class UserControllerTest extends TestCase
         $response->assertViewIs('users.edit');
     }
 
+    public function test_user_show_uses_nav_context_back_url(): void
+    {
+        $user = User::factory()->create(['role' => Role::MANAGER]);
+
+        $response = $this->actingAs($this->adminUser)
+            ->withSession([
+                'navigation' => [
+                    'user-nav' => [
+                        'updated_at' => now()->timestamp,
+                        'stack' => [
+                            route('user.index', ['page' => 2]),
+                        ],
+                    ],
+                ],
+            ])
+            ->get(route('user.show', ['user' => $user, 'nav' => 'user-nav']));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', 'user-nav');
+        $response->assertViewHas('back_url', route('user.index', ['page' => 2]));
+    }
+
     // ==================== Store - create new user ====================
 
     public function test_admin_can_create_user(): void

+ 64 - 0
tests/Unit/Services/NavigationContextServiceTest.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace Tests\Unit\Services;
+
+use App\Services\NavigationContextService;
+use Illuminate\Http\Request;
+use Tests\TestCase;
+
+class NavigationContextServiceTest extends TestCase
+{
+    public function test_builds_back_navigation_stack_from_get_requests(): void
+    {
+        session()->start();
+
+        $service = app(NavigationContextService::class);
+        $indexRequest = Request::create('https://crm.test/reclamations?filters[comment]=abc', 'GET');
+        $showRequest = Request::create('https://crm.test/reclamations/show/10', 'GET');
+
+        $token = $service->getOrCreateToken($indexRequest);
+        $service->rememberCurrentPage($indexRequest, $token);
+        $service->rememberCurrentPage($showRequest, $token);
+
+        $backUrl = $service->backUrl($showRequest, $token, 'https://crm.test/reclamations');
+        $parentUrl = $service->parentUrl($token, 'https://crm.test/reclamations');
+
+        $this->assertSame('https://crm.test/reclamations?filters%5Bcomment%5D=abc', $backUrl);
+        $this->assertSame('https://crm.test/reclamations?filters%5Bcomment%5D=abc', $parentUrl);
+    }
+
+    public function test_remember_current_page_moves_duplicate_url_to_stack_end_without_duplication(): void
+    {
+        session()->start();
+
+        $service = app(NavigationContextService::class);
+        $indexRequest = Request::create('https://crm.test/catalog?page=2', 'GET');
+        $showRequest = Request::create('https://crm.test/catalog/15?nav=test-token', 'GET');
+
+        $token = $service->getOrCreateToken($indexRequest);
+        $service->rememberCurrentPage($indexRequest, $token);
+        $service->rememberCurrentPage($showRequest, $token);
+        $service->rememberCurrentPage($indexRequest, $token);
+
+        $contexts = session('navigation');
+        $stack = $contexts[$token]['stack'] ?? [];
+
+        $this->assertSame([
+            'https://crm.test/catalog/15',
+            'https://crm.test/catalog?page=2',
+        ], $stack);
+    }
+
+    public function test_parent_url_returns_fallback_when_stack_has_single_entry(): void
+    {
+        session()->start();
+
+        $service = app(NavigationContextService::class);
+        $request = Request::create('https://crm.test/product_sku/55', 'GET');
+
+        $token = $service->getOrCreateToken($request);
+        $service->rememberCurrentPage($request, $token);
+
+        $this->assertSame('https://crm.test/product_sku', $service->parentUrl($token, 'https://crm.test/product_sku'));
+    }
+}