Pārlūkot izejas kodu

created reclamation table, relations, etc

Alexander Musikhin 7 mēneši atpakaļ
vecāks
revīzija
65b5010586

+ 1 - 1
app/Http/Controllers/Controller.php

@@ -113,7 +113,7 @@ class Controller extends BaseController
         }
 
         // set order
-        $this->data['orderBy'] = (!empty($request->order)) ? 'desc' : 'asc';
+        $this->data['orderBy'] = (empty($request->order)) ? 'desc' : 'asc';
     }
 
     /**

+ 176 - 1
app/Http/Controllers/ReclamationController.php

@@ -2,9 +2,184 @@
 
 namespace App\Http\Controllers;
 
+use App\Http\Requests\CreateReclamationRequest;
+use App\Http\Requests\StoreReclamationRequest;
+use App\Models\File;
+use App\Models\Order;
+use App\Models\Reclamation;
+use App\Models\ReclamationStatus;
+use App\Models\Role;
+use App\Models\User;
+use App\Services\FileService;
 use Illuminate\Http\Request;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\Storage;
 
 class ReclamationController extends Controller
 {
-    //
+    protected array $data = [
+        'active'    => 'reclamations',
+        'title'     => 'Рекламации',
+        'id'        => 'reclamations',
+        'header'    => [
+            'id'                        => 'ID',
+            'user_id'                   => 'Менеджер',
+            'status_id'                 => 'Статус',
+            'order-district_id'         => 'Округ',
+            'order-area_id'             => 'Район',
+            'order-object_address'      => 'Адрес объекта',
+            'create_date'               => 'Дата создания',
+            'finish_date'             => 'Дата завершения',
+            'reason'                    => 'Причина',
+            'guarantee'                 => 'Гарантии',
+            'whats_done'                => 'Что сделано',
+        ],
+        'searchFields' => [
+            'reason',
+            'guarantee',
+            'whats_done',
+        ],
+        'ranges' => [],
+    ];
+
+    public function __construct()
+    {
+        $this->data['users'] = User::query()->whereIn('role', [Role::MANAGER, Role::ADMIN])->get()->pluck('name', 'id');
+        $this->data['statuses'] = ReclamationStatus::query()->get()->pluck('name', 'id');
+    }
+
+    public function index(Request $request)
+    {
+        $model = new Reclamation();
+        // fill filters
+        $this->createFilters($model, 'user_id', 'status_id');
+        $this->createDateFilters($model, 'create_date', 'finish_date');
+
+        $q = $model::query();
+
+        $this->acceptFilters($q, $request);
+        $this->acceptSearch($q, $request);
+        $this->setSortAndOrderBy($model, $request);
+
+        $q->orderBy($this->data['sortBy'], $this->data['orderBy']);
+        $this->data['reclamations'] = $q->paginate(session('per_page', config('pagination.per_page')))->withQueryString();
+
+        return view('reclamations.index', $this->data);
+    }
+
+    public function create(CreateReclamationRequest $request, Order $order)
+    {
+        $reclamation = Reclamation::query()->create([
+            'order_id' => $order->id,
+            'user_id' => $request->user()->id,
+            'status_id' => Reclamation::STATUS_NEW,
+            'create_date' => Carbon::now(),
+            'finish_date' => Carbon::now()->addDays(30),
+        ]);
+        $skus = $request->validated('skus');
+        $reclamation->skus()->attach($skus);
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
+    public function show(Reclamation $reclamation)
+    {
+        $this->data['reclamation'] = $reclamation;
+        return view('reclamations.edit', $this->data);
+    }
+
+    public function update(StoreReclamationRequest $request, Reclamation $reclamation)
+    {
+        $data = $request->validated();
+        $reclamation->update($data);
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
+    public function uploadPhotoBefore(Request $request, Reclamation $reclamation, FileService $fileService)
+    {
+        $data = $request->validate([
+            'photo.*' => 'mimes:jpeg,jpg,png|max:8192',
+        ]);
+        $f = [];
+        foreach ($data['photo'] as $photo) {
+            $f[] = $fileService->saveUploadedFile( 'reclamations/' . $reclamation->id . '/photo_before', $photo);
+        }
+        $reclamation->photos_before()->syncWithoutDetaching($f);
+
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
+    public function uploadPhotoAfter(Request $request, Reclamation $reclamation, FileService $fileService)
+    {
+        $data = $request->validate([
+            'photo.*' => 'mimes:jpeg,jpg,png|max:8192',
+        ]);
+        $f = [];
+        foreach ($data['photo'] as $photo) {
+            $f[] = $fileService->saveUploadedFile( 'reclamations/' . $reclamation->id . '/photo_after', $photo);
+        }
+        $reclamation->photos_after()->syncWithoutDetaching($f);
+
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
+    public function deletePhotoBefore(Reclamation $reclamation, File $file, FileService $fileService)
+    {
+        $reclamation->photos_before()->detach($file);
+        Storage::disk('public')->delete($file->path);
+        $file->delete();
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
+    public function deletePhotoAfter(Reclamation $reclamation, File $file, FileService $fileService)
+    {
+        $reclamation->photos_after()->detach($file);
+        Storage::disk('public')->delete($file->path);
+        $file->delete();
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
+    public function uploadDocument(Request $request, Reclamation $reclamation, FileService $fileService)
+    {
+        $data = $request->validate([
+            'document.*' => 'file',
+        ]);
+        $f = [];
+        foreach ($data['document'] as $document) {
+            $f[] = $fileService->saveUploadedFile( 'reclamations/' . $reclamation->id . '/document', $document);
+        }
+        $reclamation->documents()->syncWithoutDetaching($f);
+
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
+    public function deleteDocument(Reclamation $reclamation, File $file)
+    {
+        $reclamation->documents()->detach($file);
+        Storage::disk('public')->delete($file->path);
+        $file->delete();
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
+    public function uploadAct(Request $request, Reclamation $reclamation, FileService $fileService)
+    {
+        $data = $request->validate([
+            'acts.*' => 'file',
+        ]);
+        $f = [];
+        foreach ($data['acts'] as $document) {
+            $f[] = $fileService->saveUploadedFile( 'reclamations/' . $reclamation->id . '/act', $document);
+        }
+        $reclamation->acts()->syncWithoutDetaching($f);
+
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
+    public function deleteAct(Reclamation $reclamation, File $file)
+    {
+        $reclamation->acts()->detach($file);
+        Storage::disk('public')->delete($file->path);
+        $file->delete();
+        return redirect()->route('reclamations.show', $reclamation);
+    }
+
 }

+ 28 - 0
app/Http/Requests/CreateReclamationRequest.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CreateReclamationRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return hasRole('admin,manager');
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules(): array
+    {
+        return [
+            'skus.*'    =>  'required|exists:products_sku,id',
+        ];
+    }
+}

+ 34 - 0
app/Http/Requests/StoreReclamationRequest.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class StoreReclamationRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return hasRole('admin,manager');
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules(): array
+    {
+        return [
+            'user_id'       => 'required|exists:users,id',
+            'status_id'     => 'required|exists:reclamation_statuses,id',
+            'reason'        => 'nullable|string',
+            'guarantee'     => 'nullable|string',
+            'whats_done'    => 'nullable|string',
+            'create_date'   => 'required|date',
+            'finish_date'   => 'required|date',
+        ];
+    }
+}

+ 5 - 1
app/Models/Order.php

@@ -164,6 +164,11 @@ class Order extends Model
         return $this->belongsToMany(File::class, 'order_statement');
     }
 
+    public function reclamations(): HasMany
+    {
+        return $this->hasMany(Reclamation::class);
+    }
+
 
 
     public function getNeeds(): array
@@ -217,7 +222,6 @@ class Order extends Model
 
             if($allMafConnected) {
                 $this->update(['order_status_id' => self::STATUS_IN_MOUNT]);
-                GenerateInstallationPack::dispatch($this, auth()->user()->id);
             }
         }
 

+ 2 - 2
app/Models/ProductSKU.php

@@ -47,7 +47,7 @@ class ProductSKU extends Model
      */
     public function product(): BelongsTo
     {
-        return $this->belongsTo(Product::class, 'product_id', 'id');
+        return $this->belongsTo(Product::class, 'product_id', 'id')->withoutGlobalScopes();
     }
 
     /**
@@ -71,6 +71,6 @@ class ProductSKU extends Model
      */
     public function maf_order(): BelongsTo
     {
-        return $this->belongsTo(MafOrder::class, 'maf_order_id', 'id');
+        return $this->belongsTo(MafOrder::class, 'maf_order_id', 'id')->withoutGlobalScopes();
     }
 }

+ 41 - 1
app/Models/Reclamation.php

@@ -8,9 +8,34 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
 class Reclamation extends Model
 {
+    const DEFAULT_SORT_BY = 'created_at';
+
+    const STATUS_NEW = 1;
+    const STATUS_IN_WORK = 2;
+    const STATUS_SUBSCRIBE_ACT = 3;
+    const STATUS_DONE = 4;
+    const STATUS_SENT = 5;
+    const STATUS_DO_DOCS = 6;
+    const STATUS_HANDOVER_TO_CHECK = 7;
+    const STATUS_PAID = 8;
+    const STATUS_CLOSED_NO_PAY = 9;
+
+    const STATUS_NAMES = [
+        self::STATUS_NEW => 'Новая',
+        self::STATUS_IN_WORK => 'В работе',
+        self::STATUS_SUBSCRIBE_ACT => 'Подпись акта',
+        self::STATUS_DONE => 'Исправлена',
+        self::STATUS_SENT => 'Отправлена',
+        self::STATUS_DO_DOCS => 'Подготовка документов',
+        self::STATUS_HANDOVER_TO_CHECK => 'Передана на проверку',
+        self::STATUS_PAID => 'Оплачена',
+        self::STATUS_CLOSED_NO_PAY => 'Закрыта, не оплачивается',
+    ];
+
     protected $fillable = [
         'order_id',
         'user_id',
+        'status_id',
         'reason',
         'guarantee',
         'whats_done',
@@ -20,7 +45,22 @@ class Reclamation extends Model
 
     public function order(): BelongsTo
     {
-        return $this->belongsTo(Order::class);
+        return $this->belongsTo(Order::class)->withoutGlobalScopes();
+    }
+
+    public function status(): BelongsTo
+    {
+        return $this->belongsTo(ReclamationStatus::class);
+    }
+
+    public function skus(): BelongsToMany
+    {
+        return $this->belongsToMany(
+            ProductSKU::class,
+            'reclamation_product_sku',
+            'reclamation_id',
+            'product_sku_id')
+            ->withoutGlobalScopes();
     }
 
     public function user(): BelongsTo

+ 12 - 0
app/Models/ReclamationStatus.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class ReclamationStatus extends Model
+{
+
+
+    public $timestamps = false;
+}

+ 28 - 0
database/migrations/2025_04_30_203324_create_reclamation_product_sku_table.php

@@ -0,0 +1,28 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('reclamation_product_sku', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('reclamation_id')->constrained('reclamations')->cascadeOnDelete();
+            $table->foreignId('product_sku_id')->constrained('products_sku')->cascadeOnDelete();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('reclamation_product_sku');
+    }
+};

+ 27 - 0
database/migrations/2025_04_30_221333_create_reclamation_statuses_table.php

@@ -0,0 +1,27 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('reclamation_statuses', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('reclamation_statuses');
+    }
+};

+ 34 - 0
database/migrations/2025_04_30_222606_add_field_to_reclamations_table.php

@@ -0,0 +1,34 @@
+<?php
+
+use App\Models\Reclamation;
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::table('reclamations', function (Blueprint $table) {
+            $table->foreignId('status_id')
+                ->after('user_id')
+                ->default(Reclamation::STATUS_NEW)
+                ->constrained('reclamation_statuses')
+                ->restrictOnDelete();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('reclamations', function (Blueprint $table) {
+            $table->dropForeign('reclamations_status_id_foreign');
+            $table->dropColumn('status_id');
+        });
+    }
+};

+ 1 - 0
database/seeders/DatabaseSeeder.php

@@ -27,5 +27,6 @@ class DatabaseSeeder extends Seeder
         $this->call(ObjectTypeSeeder::class);
         $this->call(OrderStatusSeeder::class);
         $this->call(BrigadierSeeder::class);
+        $this->call(ReclamationStatusSeeder::class);
     }
 }

+ 20 - 0
database/seeders/ReclamationStatusSeeder.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace Database\Seeders;
+
+use App\Models\Reclamation;
+use App\Models\ReclamationStatus;
+use Illuminate\Database\Seeder;
+
+class ReclamationStatusSeeder extends Seeder
+{
+    /**
+     * Run the database seeds.
+     */
+    public function run(): void
+    {
+        foreach (Reclamation::STATUS_NAMES as $id => $name) {
+            ReclamationStatus::query()->updateOrCreate(['id' => $id], ['name' => $name]);
+        }
+    }
+}

+ 1 - 0
resources/views/layouts/menu.blade.php

@@ -1,6 +1,7 @@
 <li class="nav-item"><a class="nav-link" href="{{ route('order.index') }}">Площадки</a></li>
 <li class="nav-item"><a class="nav-link" href="{{ route('product_sku.index') }}">МАФ</a></li>
 <li class="nav-item"><a class="nav-link" href="{{ route('maf_order.index') }}">Заказы МАФ</a></li>
+<li class="nav-item"><a class="nav-link" href="{{ route('reclamations.index') }}">Рекламации</a></li>
 
 <li class="nav-item"><a class="nav-link" href="{{ route('catalog.index') }}">Каталог</a></li>
 

+ 30 - 1
resources/views/orders/show.blade.php

@@ -53,7 +53,17 @@ use App\Models\Order;
                 <div>Название группы в ТГ: {{ $order->tg_group_name }}</div>
                 <div>Ссылка на группу в ТГ: {{ $order->tg_group_link }}</div>
                 <hr>
-
+                <div class="reclamations">
+                    Рекламации
+                    @foreach($order->reclamations as $reclamation)
+                        <div>
+                            <a href="{{ route('reclamations.show', $reclamation) }}">
+                                Рекламация № {{ $reclamation->id }} от {{ $reclamation->create_date }}
+                            </a>
+                        </div>
+                    @endforeach
+                </div>
+                <hr>
                 <div class="documents">
                     Документы <button class="btn btn-sm text-success" onclick="$('#upl-documents').trigger('click');"><i class="bi bi-plus-circle-fill"></i> Загрузить</button>
                     <form action="{{ route('order.upload-document', $order) }}" enctype="multipart/form-data" method="post" class="visually-hidden">
@@ -216,6 +226,10 @@ use App\Models\Order;
                             <a href="{{ route('order.get-maf', $order) }}" class="btn btn-primary btn-sm @disabled($order->ready_to_mount == 'Нет' )">Привязать все МАФы</a>
                             <a href="{{ route('order.revert-maf', $order) }}" class="btn btn-primary btn-sm">Отвязать все МАФы</a>
                             <button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#moveModal">Перенести МАФы</button>
+                            <button class="btn btn-sm btn-warning" id="create-reclamation-button">Создать рекламацию</button>
+                            <form action="{{ route('reclamations.create', $order) }}" method="post" class="visually-hidden" id="create-reclamation-form">
+                                @csrf
+                            </form>
                         </div>
                     @endif
                 </div>
@@ -292,6 +306,21 @@ use App\Models\Order;
             );
         });
 
+        $('#create-reclamation-button').on('click', function () {
+            let ids = Array();
+            $('.check-maf').each(function () {
+                if ($(this).prop('checked')) {
+                    ids.push($(this).attr('data-maf-id'));
+                    $('#create-reclamation-form').append('<input type="hidden" name="skus[]" value="'+$(this).attr('data-maf-id')+'">');
+                }
+            });
+
+            if(ids.length) {
+                $('#create-reclamation-form').submit();
+            }
+
+        });
+
 
     </script>
 @endpush

+ 212 - 0
resources/views/reclamations/edit.blade.php

@@ -0,0 +1,212 @@
+@extends('layouts.app')
+
+@section('content')
+
+    <div class="px-3">
+        <div class="row">
+            <div class="col-xl-6">
+                <h4>Рекламация</h4>
+            </div>
+            <div class="col-xl-6 text-end">
+
+            </div>
+        </div>
+        <div class="row">
+            <div class="col-xl-5 border-end">
+                <form action="{{ route('reclamations.update', $reclamation) }}" method="post">
+
+                    @csrf
+                    <input type="hidden" id="order_id" name="order_id" value="{{ $reclamation->order_id}}">
+
+                    @include('partials.input', ['name' => 'order_name', 'title' => 'Площадка', 'disabled' => true, 'value' => $reclamation->order->common_name ?? ''])
+                    @include('partials.select', ['name' => 'status_id', 'title' => 'Статус', 'options' => $statuses, 'value' => $reclamation->status_id ?? old('status_id')])
+                    @include('partials.select', ['name' => 'user_id', 'title' => 'Менеджер', 'options' => $users, 'value' => $reclamation->user_id ?? old('user_id') ?? auth()->user()->id])
+                    @include('partials.input', ['name' => 'create_date', 'title' => 'Дата создания', 'type' => 'date', 'required' => true, 'value' => $reclamation->create_date ?? date('Y-m-d')])
+                    @include('partials.input', ['name' => 'finish_date', 'title' => 'Дата завершения', 'type' => 'date', 'required' => true, 'value' => $reclamation->finish_date ?? date('Y-m-d', strtotime('+30 days'))])
+                    @include('partials.textarea', ['name' => 'reason', 'title' => 'Причина', 'size' => 6, 'value' => $reclamation->reason ?? ''])
+                    @include('partials.textarea', ['name' => 'guarantee', 'title' => 'Гарантии', 'size' => 6, 'value' => $reclamation->guarantee ?? ''])
+                    @include('partials.textarea', ['name' => 'whats_done', 'title' => 'Что сделано', 'size' => 6, 'value' => $reclamation->whats_done ?? ''])
+                    <input type="hidden" name="redirect_url" value="{{ url()->previous() }}">
+                    @include('partials.submit', ['name' => 'Сохранить', 'offset' => 5])
+                </form>
+            </div>
+            <div class="col-xl-7">
+                <table class="table">
+                    <thead>
+                    <tr>
+                        <th>Картинка</th>
+                        <th>МАФ</th>
+                        <th>Тип</th>
+                        <th>Номер заказа МАФ</th>
+                        <th>RFID</th>
+                        <th>Заводской номер</th>
+                        <th>Дата производства</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    @foreach($reclamation->skus as $p)
+                        <tr>
+                            <td>
+                                @if($p->product->image)
+                                    <a href="{{ $p->product->image }}" data-toggle="lightbox" data-gallery="maf" data-size="fullscreen">
+                                        <img src="{{ $p->product->image }}" alt="" class="img-thumbnail maf-img">
+                                    </a>
+                                @endif
+                            </td>
+                            <td>
+                                <a href="{{ route('product_sku.show', $p) }}">
+                                    {!! $p->product->article !!}
+                                </a>
+                                <br>
+                                <a class="small" href="{{ route('catalog.show', $p->product) }}">каталог</a>
+                            </td>
+                            <td>{!! $p->product->nomenclature_number !!}</td>
+                            <td>
+                                @if($p->maf_order_id)
+                                    <a href="{{ route('maf_order.show', $p->maf_order) }}">{{ $p->maf_order->order_number }}</a>
+                                @endif
+                            </td>
+                            <td>{{ $p->rfid }}</td>
+                            <td>{{ $p->factory_number }}</td>
+                            <td>{{ $p->manufacture_date }}</td>
+                        </tr>
+                    @endforeach
+                    </tbody>
+                </table>
+
+
+
+                <div class="documents">
+                    Документы
+                    <button class="btn btn-sm text-success" onclick="$('#upl-documents').trigger('click');"><i
+                                class="bi bi-plus-circle-fill"></i> Загрузить
+                    </button>
+                    <form action="{{ route('reclamations.upload-document', $reclamation) }}"
+                          enctype="multipart/form-data" method="post" class="visually-hidden">
+                        @csrf
+                        <input required type="file" id="upl-documents" onchange="$(this).parent().submit()" multiple
+                               name="document[]" class="form-control form-control-sm">
+                    </form>
+                    <div class="row my-2 g-1">
+                        @foreach($reclamation->documents as $document)
+                            <div class="col-12">
+                                <a href="{{ $document->link }}" target="_blank">
+                                    {{ $document->original_name }}
+                                </a>
+                                @if(hasRole('admin'))
+                                    <i class="bi bi-x-circle-fill fs-6 text-danger cursor-pointer ms-2"
+                                       onclick="if(confirm('Удалить?')) $('#document-{{ $document->id }}').submit()"
+                                       title="Удалить"></i>
+                                @endif
+                                <form action="{{ route('reclamations.delete-document', [$reclamation, $document]) }}"
+                                      method="POST" id="document-{{ $document->id }}" class="visually-hidden">
+                                    @csrf
+                                    @method('DELETE')
+                                </form>
+                            </div>
+                        @endforeach
+                    </div>
+                </div>
+                <hr>
+                <div class="acts">
+                    Акты
+                    <button class="btn btn-sm text-success" onclick="$('#upl-acts').trigger('click');"><i
+                                class="bi bi-plus-circle-fill"></i> Загрузить
+                    </button>
+                    <form action="{{ route('reclamations.upload-act', $reclamation) }}" enctype="multipart/form-data"
+                          method="post" class="visually-hidden">
+                        @csrf
+                        <input required type="file" id="upl-acts" onchange="$(this).parent().submit()" multiple
+                               name="acts[]" class="form-control form-control-sm">
+                    </form>
+                    <div class="row my-2 g-1">
+                        @foreach($reclamation->acts as $act)
+                            <div class="col-12">
+                                <a href="{{ $act->link }}" target="_blank">
+                                    {{ $act->original_name }}
+                                </a>
+                                @if(hasRole('admin'))
+                                    <i class="bi bi-x-circle-fill fs-6 text-danger cursor-pointer ms-2"
+                                       onclick="if(confirm('Удалить?')) $('#act-{{ $act->id }}').submit()"
+                                       title="Удалить"></i>
+                                @endif
+                                <form action="{{ route('reclamations.delete-act', [$reclamation, $act]) }}" method="POST"
+                                      id="act-{{ $act->id }}" class="visually-hidden">
+                                    @csrf
+                                    @method('DELETE')
+                                </form>
+                            </div>
+                        @endforeach
+                    </div>
+                </div>
+                <hr>
+                <div class="photo_before">
+                    <a href="#photos_before" data-bs-toggle="collapse">Фотографии проблемы ({{ $reclamation->photos_before->count() }})</a>
+                    <button class="btn btn-sm text-success" onclick="$('#upl-photo-before').trigger('click');"><i
+                                class="bi bi-plus-circle-fill"></i> Загрузить
+                    </button>
+                    <form action="{{ route('reclamations.upload-photo-before', $reclamation) }}"
+                          enctype="multipart/form-data" method="post" class="visually-hidden">
+                        @csrf
+                        <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">
+                    </form>
+                    <div class="row my-2 g-1 collapse" id="photos_before">
+                        @foreach($reclamation->photos_before as $photo)
+                            <div class="col-4">
+                                <a href="{{ $photo->link }}"
+                                   data-toggle="lightbox" data-gallery="photos-before" data-size="fullscreen">
+                                    <img class="img-thumbnail" src="{{ $photo->link }}" alt="">
+                                </a>
+                                @if(hasRole('admin'))
+                                    <i class="bi bi-x-circle-fill fs-6 text-danger cursor-pointer rm-but"
+                                       onclick="if(confirm('Удалить фото?')) $('#photo-{{ $photo->id }}').submit()"
+                                       title="Удалить"></i>
+                                @endif
+                                <form action="{{ route('reclamations.delete-photo-before', [$reclamation, $photo]) }}"
+                                      method="POST" id="photo-{{ $photo->id }}" class="visually-hidden">
+                                    @csrf
+                                    @method('DELETE')
+                                </form>
+                            </div>
+                        @endforeach
+                    </div>
+                </div>
+                <hr>
+                <div class="photo_after">
+                    <a href="#photos_after" data-bs-toggle="collapse">Фотографии после устранения ({{ $reclamation->photos_after->count() }})</a>
+                    <button class="btn btn-sm text-success" onclick="$('#upl-photo-after').trigger('click');"><i
+                                class="bi bi-plus-circle-fill"></i> Загрузить
+                    </button>
+                    <form action="{{ route('reclamations.upload-photo-after', $reclamation) }}"
+                          enctype="multipart/form-data" method="post" class="visually-hidden">
+                        @csrf
+                        <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">
+                    </form>
+                    <div class="row my-2 g-1 collapse" id="photos_after">
+                        @foreach($reclamation->photos_after as $photo)
+                            <div class="col-4">
+                                <a href="{{ $photo->link }}"
+                                   data-toggle="lightbox" data-gallery="photos-after" data-size="fullscreen">
+                                    <img class="img-thumbnail" src="{{ $photo->link }}" alt="">
+                                </a>
+                                @if(hasRole('admin'))
+                                    <i class="bi bi-x-circle-fill fs-6 text-danger cursor-pointer rm-but"
+                                       onclick="if(confirm('Удалить фото?')) $('#photo-{{ $photo->id }}').submit()"
+                                       title="Удалить"></i>
+                                @endif
+                                <form action="{{ route('reclamations.delete-photo-after', [$reclamation, $photo]) }}"
+                                      method="POST" id="photo-{{ $photo->id }}" class="visually-hidden">
+                                    @csrf
+                                    @method('DELETE')
+                                </form>
+                            </div>
+                        @endforeach
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+@endsection
+

+ 33 - 0
resources/views/reclamations/index.blade.php

@@ -0,0 +1,33 @@
+@extends('layouts.app')
+
+@section('content')
+
+    <div class="row mb-3">
+        <div class="col-6">
+            <h3>Рекламации</h3>
+        </div>
+        <div class="col-6 text-end">
+
+
+        </div>
+    </div>
+
+
+    @include('partials.table', [
+        'id'        => $id,
+        'header'    => $header,
+        'strings'   => $reclamations,
+        'routeName' => 'reclamations.show',
+    ])
+
+    <div class="row pt-3 px-3">
+        <div class="col-12 pagination">
+            {{ $reclamations->links() }}
+        </div>
+    </div>
+
+
+    @if($errors->count())
+        @dump($errors)
+    @endif
+@endsection

+ 17 - 0
routes/web.php

@@ -6,6 +6,7 @@ use App\Http\Controllers\MafOrderController;
 use App\Http\Controllers\OrderController;
 use App\Http\Controllers\ProductController;
 use App\Http\Controllers\ProductSKUController;
+use App\Http\Controllers\ReclamationController;
 use App\Http\Controllers\ResponsibleController;
 use App\Http\Controllers\UserController;
 use App\Models\Role;
@@ -98,6 +99,12 @@ Route::middleware('auth:web')->group(function () {
         Route::delete('order/delete-statement/{order}/{file}', [OrderController::class, 'deleteStatement'])->name('order.delete-statement');
         Route::delete('catalog/delete-certificate/{product}/{file}', [ProductController::class, 'deleteCertificate'])->name('catalog.delete-certificate');
         Route::delete('product_sku/delete-passport/{product_sku}/{file}', [ProductSKUController::class, 'deletePassport'])->name('product-sku.delete-passport');
+
+        Route::delete('reclamations/delete-photo-before/{reclamation}/{file}', [ReclamationController::class, 'deletePhotoBefore'])->name('reclamations.delete-photo-before');
+        Route::delete('reclamations/delete-photo-after/{reclamation}/{file}', [ReclamationController::class, 'deletePhotoAfter'])->name('reclamations.delete-photo-after');
+        Route::delete('reclamations/delete-document/{reclamation}/{file}', [ReclamationController::class, 'deleteDocument'])->name('reclamations.delete-document');
+        Route::delete('reclamations/delete-act/{reclamation}/{file}', [ReclamationController::class, 'deleteAct'])->name('reclamations.delete-act');
+
     });
 
     Route::get('order/generate-installation-pack/{order}', [OrderController::class, 'generateInstallationPack'])->name('order.generate-installation-pack');
@@ -116,6 +123,16 @@ Route::middleware('auth:web')->group(function () {
     Route::delete('maf_orders/delete/{maf_order}', [MafOrderController::class, 'destroy'])->name('maf_order.delete');
     Route::post('maf_orders/set_in_stock/{maf_order}', [MafOrderController::class, 'setInStock'])->name('maf_order.set_in_stock');
 
+    Route::get('reclamations', [ReclamationController::class, 'index'])->name('reclamations.index');
+    Route::post('reclamations/create/{order}', [ReclamationController::class, 'create'])->name('reclamations.create');
+    Route::get('reclamations/show/{reclamation}', [ReclamationController::class, 'show'])->name('reclamations.show');
+    Route::post('reclamations/update/{reclamation}', [ReclamationController::class, 'update'])->name('reclamations.update');
+
+    Route::post('reclamations/{reclamation}/upload-document', [ReclamationController::class, 'uploadDocument'])->name('reclamations.upload-document');
+    Route::post('reclamations/{reclamation}/upload-photo-before', [ReclamationController::class, 'uploadPhotoBefore'])->name('reclamations.upload-photo-before');
+    Route::post('reclamations/{reclamation}/upload-photo-after', [ReclamationController::class, 'uploadPhotoAfter'])->name('reclamations.upload-photo-after');
+    Route::post('reclamations/{reclamation}/upload-act', [ReclamationController::class, 'uploadAct'])->name('reclamations.upload-act');
+
     // ajax search products
     Route::get('product/search', [ProductController::class, 'search'])->name('product.search');