Jelajahi Sumber

fix filters

Alexander Musikhin 3 minggu lalu
induk
melakukan
2e4a2f3470

+ 29 - 7
app/Http/Controllers/Controller.php

@@ -46,15 +46,20 @@ class Controller extends BaseController
     protected function createRangeFilters(Model $model, string ...$columnNames): void
     {
         foreach ($columnNames as $columnName) {
+            $rangeKey = $columnName;
+            if (!isset($this->data['header'][$columnName]) && isset($this->data['header'][$columnName . '_txt'])) {
+                $rangeKey = $columnName . '_txt';
+            }
+            $title = $this->data['header'][$rangeKey] ?? $columnName;
             if(str_ends_with($columnName, '_price')) {
-                $this->data['ranges'][$columnName] = [
-                    'title' => $this->data['header'][$columnName],
+                $this->data['ranges'][$rangeKey] = [
+                    'title' => $title,
                     'min'   => $model::query()->min($columnName) / 100,
                     'max'   => $model::query()->max($columnName) / 100,
                 ];
             } else {
-                $this->data['ranges'][$columnName] =  [
-                    'title' => $this->data['header'][$columnName ],
+                $this->data['ranges'][$rangeKey] =  [
+                    'title' => $title,
                     'min'   => $model::query()->min($columnName),
                     'max'   => $model::query()->max($columnName),
                 ];
@@ -180,13 +185,14 @@ class Controller extends BaseController
                 $toValue = $request->filters[$columnName . '_to'] ?? null;
 
                 // Для price-полей значения в форме в рублях, в БД в копейках
-                $multiplier = str_ends_with($columnName, '_price') ? 100 : 1;
+                $dbColumn = str_ends_with($columnName, '_txt') ? substr($columnName, 0, -4) : $columnName;
+                $multiplier = str_ends_with($dbColumn, '_price') ? 100 : 1;
 
                 if($fromValue !== null && $fromValue !== '') {
-                    $query->where($columnName, '>=', $fromValue * $multiplier);
+                    $query->where($dbColumn, '>=', $fromValue * $multiplier);
                 }
                 if($toValue !== null && $toValue !== '') {
-                    $query->where($columnName, '<=', $toValue * $multiplier);
+                    $query->where($dbColumn, '<=', $toValue * $multiplier);
                 }
             }
         }
@@ -220,6 +226,22 @@ class Controller extends BaseController
             $filterValue = implode('||', $mapped);
         }
 
+        // Для цен: значения в рублях, в БД в копейках
+        if (str_ends_with($dbColumn, '_price')) {
+            $parts = explode('||', $filterValue);
+            $mapped = array_map(function ($v) {
+                if ($v === '-пусто-') {
+                    return $v;
+                }
+                $normalized = str_replace(',', '.', (string)$v);
+                if (!is_numeric($normalized)) {
+                    return $v;
+                }
+                return (string) round(((float)$normalized) * 100);
+            }, $parts);
+            $filterValue = implode('||', $mapped);
+        }
+
         return [$dbColumn, $filterValue];
     }
 

+ 21 - 1
app/Http/Controllers/FilterController.php

@@ -18,7 +18,7 @@ class FilterController extends Controller
         'responsibles'  => 'responsibles',
         'users'         => 'users',
         'contracts'     => 'contracts',
-        'spare_parts'   => 'spare_parts',
+        'spare_parts'   => 'spare_parts_view',
         'spare_part_orders' => 'spare_part_orders_view',
     ];
 
@@ -35,6 +35,16 @@ class FilterController extends Controller
             'status_name'         => 'status',
             'with_documents_text' => 'with_documents',
         ],
+        'spare_parts' => [
+            'customer_price_txt' => 'customer_price',
+            'expertise_price_txt' => 'expertise_price',
+            'purchase_price_txt' => 'purchase_price',
+        ],
+        'products' => [
+            'product_price_txt' => 'product_price',
+            'installation_price_txt' => 'installation_price',
+            'total_price_txt' => 'total_price',
+        ],
     ];
 
     /**
@@ -97,6 +107,16 @@ class FilterController extends Controller
 
             $result = $q->orderBy($dbColumn)->get()->pluck($dbColumn)->toArray();
 
+            // Конвертация цен из копеек в рубли для отображения
+            if (str_ends_with($dbColumn, '_price')) {
+                $result = array_map(function ($val) {
+                    if ($val === null) {
+                        return $val;
+                    }
+                    return $val / 100;
+                }, $result);
+            }
+
             // Применяем маппинг значений, если есть
             if (isset(self::VALUE_MAP[$table][$dbColumn])) {
                 $map = self::VALUE_MAP[$table][$dbColumn];

+ 3 - 3
app/Http/Controllers/ProductController.php

@@ -30,9 +30,9 @@ class ProductController extends Controller
             'type'                      => 'Тип',
             'manufacturer_name'         => 'Наименование производителя',
             'sizes'                     => 'Размеры',
-            'product_price'             => 'Цена товара',
-            'installation_price'        => 'Цена установки',
-            'total_price'               => 'Итоговая цена',
+            'product_price_txt'         => 'Цена товара',
+            'installation_price_txt'    => 'Цена установки',
+            'total_price_txt'           => 'Итоговая цена',
             'note'                      => 'Примечания',
             'created_at'                => 'Дата создания',
             'certificate_id'            => 'Сертификат',

+ 15 - 0
app/Http/Controllers/ReclamationController.php

@@ -110,6 +110,21 @@ class ReclamationController extends Controller
         return redirect()->route('reclamations.show', $reclamation->id);
     }
 
+    public function updateStatus(Request $request, Reclamation $reclamation)
+    {
+        if (!hasRole('admin,manager')) {
+            abort(403);
+        }
+
+        $validated = $request->validate([
+            'status_id' => 'required|exists:reclamation_statuses,id',
+        ]);
+
+        $reclamation->update(['status_id' => $validated['status_id']]);
+
+        return response()->noContent();
+    }
+
     public function delete(Reclamation $reclamation)
     {
         $reclamation->delete();

+ 2 - 1
app/Http/Controllers/SparePartController.php

@@ -7,6 +7,7 @@ use App\Jobs\Export\ExportSparePartsJob;
 use App\Jobs\Import\ImportSparePartsJob;
 use App\Models\Import;
 use App\Models\SparePart;
+use App\Models\SparePartsView;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\File;
@@ -48,7 +49,7 @@ class SparePartController extends Controller
     public function index(Request $request)
     {
         session(['gp_spare_parts' => $request->query()]);
-        $model = new SparePart();
+        $model = new SparePartsView();
 
         // Для админа добавляем колонку цены закупки
         if (hasRole('admin')) {

+ 43 - 0
app/Models/SparePartsView.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Models;
+
+class SparePartsView extends SparePart
+{
+    protected $table = 'spare_parts_view';
+
+    public $timestamps = false;
+
+    public function pricingCodes(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
+    {
+        return $this->belongsToMany(PricingCode::class, 'spare_part_pricing_code', 'spare_part_id', 'pricing_code_id')
+            ->withTimestamps();
+    }
+
+    public function getQuantityWithoutDocsAttribute(): int
+    {
+        if (array_key_exists('quantity_without_docs', $this->attributes)) {
+            return (int) $this->attributes['quantity_without_docs'];
+        }
+
+        return parent::getQuantityWithoutDocsAttribute();
+    }
+
+    public function getQuantityWithDocsAttribute(): int
+    {
+        if (array_key_exists('quantity_with_docs', $this->attributes)) {
+            return (int) $this->attributes['quantity_with_docs'];
+        }
+
+        return parent::getQuantityWithDocsAttribute();
+    }
+
+    public function getTotalQuantityAttribute(): int
+    {
+        if (array_key_exists('total_quantity', $this->attributes)) {
+            return (int) $this->attributes['total_quantity'];
+        }
+
+        return parent::getTotalQuantityAttribute();
+    }
+}

+ 56 - 0
database/migrations/2026_02_12_130001_create_spare_parts_view.php

@@ -0,0 +1,56 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Support\Facades\DB;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        DB::statement("DROP VIEW IF EXISTS spare_parts_view");
+
+        DB::statement("
+            CREATE VIEW spare_parts_view AS
+            SELECT
+                sp.*,
+                COALESCE(spo_agg.physical_stock_without_docs, 0) AS physical_stock_without_docs,
+                COALESCE(spo_agg.physical_stock_with_docs, 0) AS physical_stock_with_docs,
+                COALESCE(res_agg.reserved_without_docs, 0) AS reserved_without_docs,
+                COALESCE(res_agg.reserved_with_docs, 0) AS reserved_with_docs,
+                (COALESCE(spo_agg.physical_stock_without_docs, 0) - COALESCE(res_agg.reserved_without_docs, 0)) AS quantity_without_docs,
+                (COALESCE(spo_agg.physical_stock_with_docs, 0) - COALESCE(res_agg.reserved_with_docs, 0)) AS quantity_with_docs,
+                ((COALESCE(spo_agg.physical_stock_without_docs, 0) - COALESCE(res_agg.reserved_without_docs, 0))
+                 + (COALESCE(spo_agg.physical_stock_with_docs, 0) - COALESCE(res_agg.reserved_with_docs, 0))) AS total_quantity
+            FROM spare_parts sp
+            LEFT JOIN (
+                SELECT
+                    spare_part_id,
+                    SUM(CASE WHEN status = 'in_stock' AND with_documents = 0 THEN available_qty ELSE 0 END) AS physical_stock_without_docs,
+                    SUM(CASE WHEN status = 'in_stock' AND with_documents = 1 THEN available_qty ELSE 0 END) AS physical_stock_with_docs
+                FROM spare_part_orders
+                WHERE deleted_at IS NULL
+                GROUP BY spare_part_id
+            ) spo_agg ON spo_agg.spare_part_id = sp.id
+            LEFT JOIN (
+                SELECT
+                    spare_part_id,
+                    SUM(CASE WHEN status = 'active' AND with_documents = 0 THEN reserved_qty ELSE 0 END) AS reserved_without_docs,
+                    SUM(CASE WHEN status = 'active' AND with_documents = 1 THEN reserved_qty ELSE 0 END) AS reserved_with_docs
+                FROM reservations
+                GROUP BY spare_part_id
+            ) res_agg ON res_agg.spare_part_id = sp.id
+            WHERE sp.deleted_at IS NULL
+        ");
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        DB::statement("DROP VIEW IF EXISTS spare_parts_view");
+    }
+};

+ 39 - 1
resources/views/partials/table.blade.php

@@ -80,7 +80,12 @@
             <tr>
                 @foreach($header as $headerName => $headerTitle)
                     <td class="column_{{$headerName}}"
-                        @if(isset($routeName) && !str_contains($headerName, 'image') && !str_contains($headerName, 'order_status_name'))  onclick="location.href='{{ route($routeName, $string->id) }}'" @endif>
+                        @if(isset($routeName)
+                            && !str_contains($headerName, 'image')
+                            && !str_contains($headerName, 'order_status_name')
+                            && !($id === 'reclamations' && $headerName === 'status_name'))
+                            onclick="location.href='{{ route($routeName, $string->id) }}'"
+                        @endif>
                         @if(str_contains($headerName, '-'))
                             @php
                                 list($rel, $field) = explode('-', $headerName);
@@ -133,6 +138,16 @@
                                     <option value="{{ $statusId }}" @selected($statusName == $string->$headerName)>{{ $statusName }}</option>
                                 @endforeach
                             </select>
+                        @elseif($id === 'reclamations' && $headerName === 'status_name')
+                            <select name="status_id"
+                                    data-reclamation-id="{{ $string->id }}"
+                                    data-url="{{ route('reclamations.update-status', $string->id) }}"
+                                    @disabled(!hasRole('admin,manager'))
+                                    class="change-reclamation-status form-control form-control-sm">
+                                @foreach($statuses as $statusId => $statusName)
+                                    <option value="{{ $statusId }}" @selected($statusId == $string->status_id)>{{ $statusName }}</option>
+                                @endforeach
+                            </select>
                         @elseif($headerName === 'tsn_number' && $string->$headerName)
                             <span data-bs-toggle="tooltip"
                                   data-bs-placement="top"
@@ -450,6 +465,29 @@
             );
         });
 
+        $('.change-reclamation-status').on('change', function () {
+            let statusId = $(this).val();
+            let url = $(this).attr('data-url');
+
+            $.post(
+                url,
+                {
+                    '_token' : '{{ csrf_token() }}',
+                    status_id: statusId
+                },
+                function () {
+                    $('.alerts').append(
+                        '<div class="main-alert alert alert-success" role="alert">Обновлён статус рекламации!</div>'
+                    );
+                    setTimeout(function () {
+                        $('.main-alert').fadeTo(2000, 500).slideUp(500, function () {
+                            $(".main-alert").slideUp(500);
+                        })
+                    }, 3000);
+                }
+            );
+        });
+
         $(document).ready(async function () {
             let height1 = $('.das').innerHeight();
             let height2 = $('.catalog').innerHeight();

+ 2 - 1
routes/web.php

@@ -163,6 +163,7 @@ Route::middleware('auth:web')->group(function () {
         // рекламации
         Route::post('reclamations/create/{order}', [ReclamationController::class, 'create'])->name('reclamations.create');
         Route::post('reclamations/update/{reclamation}', [ReclamationController::class, 'update'])->name('reclamations.update');
+        Route::post('reclamations/{reclamation}/update-status', [ReclamationController::class, 'updateStatus'])->name('reclamations.update-status');
         Route::post('reclamations/{reclamation}/upload-document', [ReclamationController::class, 'uploadDocument'])->name('reclamations.upload-document');
         Route::post('reclamations/{reclamation}/upload-act', [ReclamationController::class, 'uploadAct'])->name('reclamations.upload-act');
         Route::post('reclamations/{reclamation}/update-details', [ReclamationController::class, 'updateDetails'])->name('reclamations.update-details');
@@ -311,4 +312,4 @@ Route::middleware('auth:web')->group(function () {
         Route::delete('/{pricingCode}', [PricingCodeController::class, 'destroy'])->name('destroy');
     });
 
-});
+});