Просмотр исходного кода

Add bulk spare part order stocking

Alexander Musikhin 4 дней назад
Родитель
Сommit
944cb1f341

+ 36 - 0
app/Http/Controllers/SparePartOrderController.php

@@ -10,6 +10,7 @@ use App\Models\SparePartOrdersView;
 use App\Services\SparePartIssueService;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
 
 class SparePartOrderController extends Controller
 {
@@ -88,6 +89,13 @@ class SparePartOrderController extends Controller
         $this->applyStableSorting($q);
 
         $this->data['spare_part_orders'] = $q->paginate($this->data['per_page'])->withQueryString();
+        $this->data['order_numbers'] = SparePartOrder::query()
+            ->where('status', SparePartOrder::STATUS_ORDERED)
+            ->whereNotNull('order_number')
+            ->where('order_number', '!=', '')
+            ->orderBy('order_number')
+            ->distinct()
+            ->pluck('order_number');
         $this->data['strings'] = $this->data['spare_part_orders'];
         $this->data['tab'] = 'orders';
 
@@ -197,6 +205,34 @@ class SparePartOrderController extends Controller
             ->with(['success' => 'Статус изменён на "На складе"!']);
     }
 
+    /**
+     * Изменить статус всех заказов с одинаковым номером на "На складе"
+     */
+    public function setOrderInStock(Request $request): RedirectResponse
+    {
+        $validated = $request->validate([
+            'bulk_order_number' => 'required|string',
+        ]);
+
+        $orderNumber = trim((string) $validated['bulk_order_number']);
+        $query = SparePartOrder::query()
+            ->where('status', SparePartOrder::STATUS_ORDERED)
+            ->where('order_number', $orderNumber);
+
+        if (!$query->exists()) {
+            throw ValidationException::withMessages([
+                'bulk_order_number' => 'Заказ со статусом "Заказано" не найден в выбранном году.',
+            ]);
+        }
+
+        $query->update([
+            'status' => SparePartOrder::STATUS_IN_STOCK,
+        ]);
+
+        return redirect()->route('spare_part_orders.index', session('gp_spare_part_orders'))
+            ->with(['success' => 'Все позиции заказа переведены в статус "На складе"!']);
+    }
+
     /**
      * Коррекция остатка (инвентаризация)
      */

+ 43 - 0
resources/views/spare_parts/index.blade.php

@@ -82,6 +82,9 @@
                     <div class="mb-3">
                         @if(hasRole('admin,manager'))
                             <a href="{{ route('spare_part_orders.create') }}" class="btn btn-sm btn-primary">Создать заказ</a>
+                            <button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#setOrderInStockModal">
+                                Отгрузить весь заказ
+                            </button>
                         @endif
                     </div>
 
@@ -307,6 +310,38 @@
 </div>
 @endif
 
+@if(hasRole('admin,manager') && ($tab ?? '') === 'orders')
+<div class="modal fade" id="setOrderInStockModal" tabindex="-1" aria-labelledby="setOrderInStockModalLabel" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <form action="{{ route('spare_part_orders.set_order_in_stock') }}" method="POST">
+                @csrf
+                <div class="modal-header">
+                    <h1 class="modal-title fs-5" id="setOrderInStockModalLabel">Отгрузить весь заказ</h1>
+                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
+                </div>
+                <div class="modal-body">
+                    <label for="bulk_order_number" class="form-label">Номер заказа</label>
+                    <select class="form-select @error('bulk_order_number') is-invalid @enderror" id="bulk_order_number" name="bulk_order_number" required>
+                        <option value="">Выберите номер заказа</option>
+                        @foreach($order_numbers ?? [] as $order_number)
+                            <option value="{{ $order_number }}" @selected(old('bulk_order_number') === $order_number)>{{ $order_number }}</option>
+                        @endforeach
+                    </select>
+                    @error('bulk_order_number')
+                    <div class="invalid-feedback d-block">{{ $message }}</div>
+                    @enderror
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Отмена</button>
+                    <button type="submit" class="btn btn-primary btn-sm">Отгрузить заказ</button>
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+@endif
+
 @push('scripts')
 <script type="module">
     function waitForJQuery(callback) {
@@ -333,6 +368,14 @@
 
             window.location.href = url;
         });
+
+        @if($errors->has('bulk_order_number') && hasRole('admin,manager') && ($tab ?? '') === 'orders')
+            const setOrderInStockModalElement = document.getElementById('setOrderInStockModal');
+            if (setOrderInStockModalElement) {
+                const setOrderInStockModal = new bootstrap.Modal(setOrderInStockModalElement);
+                setOrderInStockModal.show();
+            }
+        @endif
     });
 </script>
 @endpush

+ 1 - 0
routes/web.php

@@ -333,6 +333,7 @@ Route::middleware('auth:web')->group(function () {
         Route::delete('/{sparePartOrder}', [SparePartOrderController::class, 'destroy'])->name('destroy')->middleware('role:admin');
         Route::post('/{sparePartOrder}/ship', [SparePartOrderController::class, 'ship'])->name('ship');
         Route::post('/{sparePartOrder}/set-in-stock', [SparePartOrderController::class, 'setInStock'])->name('set_in_stock');
+        Route::post('/set-order-in-stock', [SparePartOrderController::class, 'setOrderInStock'])->name('set_order_in_stock');
         Route::post('/{sparePartOrder}/correct', [SparePartOrderController::class, 'correct'])->name('correct')->middleware('role:admin');
     });
 

+ 86 - 0
tests/Feature/SparePartOrderControllerTest.php

@@ -70,6 +70,36 @@ class SparePartOrderControllerTest extends TestCase
         $response->assertViewIs('spare_parts.index');
     }
 
+    public function test_index_passes_only_ordered_order_numbers_to_view(): void
+    {
+        SparePartOrder::factory()->ordered()->create([
+            'order_number' => 'SP-BULK-001',
+        ]);
+        SparePartOrder::factory()->ordered()->create([
+            'order_number' => 'SP-BULK-001',
+        ]);
+        SparePartOrder::factory()->ordered()->create([
+            'order_number' => 'SP-BULK-002',
+        ]);
+        SparePartOrder::factory()->inStock()->create([
+            'order_number' => 'SP-IN-STOCK-001',
+        ]);
+        SparePartOrder::factory()->ordered()->create([
+            'order_number' => '',
+        ]);
+        SparePartOrder::factory()->ordered()->create([
+            'order_number' => null,
+        ]);
+
+        $response = $this->actingAs($this->adminUser)
+            ->get(route('spare_part_orders.index'));
+
+        $response->assertStatus(200);
+        $response->assertViewHas('order_numbers', function ($orderNumbers) {
+            return $orderNumbers->values()->all() === ['SP-BULK-001', 'SP-BULK-002'];
+        });
+    }
+
     // ==================== Show ====================
 
     public function test_admin_can_view_spare_part_order(): void
@@ -319,6 +349,62 @@ class SparePartOrderControllerTest extends TestCase
         $response->assertRedirect(route('login'));
     }
 
+    public function test_admin_can_set_whole_order_in_stock_by_order_number(): void
+    {
+        $first = SparePartOrder::factory()->ordered()->create([
+            'order_number' => 'SP-BULK-100',
+        ]);
+        $second = SparePartOrder::factory()->ordered()->create([
+            'order_number' => 'SP-BULK-100',
+        ]);
+        $sameNumberInStock = SparePartOrder::factory()->inStock()->create([
+            'order_number' => 'SP-BULK-100',
+        ]);
+        $other = SparePartOrder::factory()->ordered()->create([
+            'order_number' => 'SP-BULK-200',
+        ]);
+
+        $response = $this->actingAs($this->adminUser)
+            ->post(route('spare_part_orders.set_order_in_stock'), [
+                'bulk_order_number' => 'SP-BULK-100',
+            ]);
+
+        $response->assertRedirect(route('spare_part_orders.index'));
+        $response->assertSessionHas('success');
+        $this->assertDatabaseHas('spare_part_orders', [
+            'id' => $first->id,
+            'status' => SparePartOrder::STATUS_IN_STOCK,
+        ]);
+        $this->assertDatabaseHas('spare_part_orders', [
+            'id' => $second->id,
+            'status' => SparePartOrder::STATUS_IN_STOCK,
+        ]);
+        $this->assertDatabaseHas('spare_part_orders', [
+            'id' => $sameNumberInStock->id,
+            'status' => SparePartOrder::STATUS_IN_STOCK,
+        ]);
+        $this->assertDatabaseHas('spare_part_orders', [
+            'id' => $other->id,
+            'status' => SparePartOrder::STATUS_ORDERED,
+        ]);
+    }
+
+    public function test_bulk_set_order_in_stock_validates_selected_number_has_ordered_rows(): void
+    {
+        SparePartOrder::factory()->inStock()->create([
+            'order_number' => 'SP-NOT-ORDERED',
+        ]);
+
+        $response = $this->from(route('spare_part_orders.index'))
+            ->actingAs($this->adminUser)
+            ->post(route('spare_part_orders.set_order_in_stock'), [
+                'bulk_order_number' => 'SP-NOT-ORDERED',
+            ]);
+
+        $response->assertRedirect(route('spare_part_orders.index'));
+        $response->assertSessionHasErrors('bulk_order_number');
+    }
+
     // ==================== Ship ====================
 
     public function test_admin_can_ship_spare_part_order(): void