Merge branch 'develop' into trunk

This commit is contained in:
Noah Lancien 2025-06-19 11:48:17 +02:00
commit 52386bbfd4
12 changed files with 162 additions and 63 deletions

View file

@ -28,11 +28,11 @@ class SiteController extends Controller
if ($user->isAdmin()) {
/** @var \Illuminate\Database\Eloquent\Builder $siteModels */
$siteModels = Site::query();
$siteModels = Site::query()->with(['invoices', 'tickets']);
} else {
/** @var \Illuminate\Database\Eloquent\Builder $siteModels */
$siteModels = Site::whereHas('users', function (\Illuminate\Database\Eloquent\Builder $query) use ($user) {
$query->where('id', $user->id);
$query->where('id', $user->id)->with(['invoices', 'tickets']);
});
}

View file

@ -517,9 +517,9 @@ class TicketController extends Controller
return back()->with('error', trans('Le ticket que vous avez renseigné ne fait pas partie du même site.'));
}
if (!$ticket->linkedTicket()->where('linked_ticket_id', $linkedTicket->id)->exists()) {
$ticket->linkedTicket()->attach($linkedTicket->id);
$linkedTicket->linkedTicket()->attach($ticket->id);
if (!$ticket->linkedTickets()->where('linked_ticket_id', $linkedTicket->id)->exists()) {
$ticket->linkedTickets()->attach($linkedTicket->id);
$linkedTicket->linkedTickets()->attach($ticket->id);
return back()->with('success', trans('Les tickets ont été liés avec succès.'));
}
@ -536,8 +536,8 @@ class TicketController extends Controller
*/
public function unlinkTicket(Ticket $ticket, Ticket $linkedTicket)
{
$ticket->linkedTicket()->detach($linkedTicket->id);
$linkedTicket->linkedTicket()->detach($ticket->id);
$ticket->linkedTickets()->detach($linkedTicket->id);
$linkedTicket->linkedTickets()->detach($ticket->id);
return back()->with('warning', trans('Le lien entre les tickets à été supprimé.'));
}

View file

@ -10,6 +10,7 @@ use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Mccarlosen\LaravelMpdf\LaravelMpdf;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* @property string $ref
@ -32,6 +33,7 @@ use Mccarlosen\LaravelMpdf\LaravelMpdf;
class Invoice extends Model
{
use HasFactory;
use SoftDeletes;
/**
* The attributes that are fillable.

View file

@ -57,6 +57,10 @@ class Site extends Model
static::updating(function (self $model) {
self::manageFields($model);
});
static::deleted(function ($site) {
$site->invoices()->delete();
$site->tickets()->delete();
});
}
// * METHODS
@ -84,7 +88,7 @@ class Site extends Model
* HasMany Message relation
* The users of type client that would have access to this site tickets
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
* @return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function users()
{
@ -101,6 +105,16 @@ class Site extends Model
return $this->hasMany(Ticket::class, 'site_id');
}
/**
* HasMany Invoices relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function invoices()
{
return $this->hasMany(Invoice::class, 'site_id');
}
/**
* BelongsTo Brand relation
*

View file

@ -16,12 +16,14 @@ use App\Models\Ticket\TicketType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
class Ticket extends Model
{
use HasFactory;
use SoftDeletes;
/**
* The attributes that are fillable.

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->dropColumn('deleted_at');
});
}
};

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->dropColumn('deleted_at');
});
}
};

View file

@ -5,9 +5,8 @@ window.addEventListener("DOMContentLoaded", () => {
/**
* Popup delete item.
*/
const elements = document.getElementsByClassName(
"confirmDeleteTS"
) as HTMLCollectionOf<Element>;
const elements = document.getElementsByClassName("confirmDeleteTS") as HTMLCollectionOf<Element>;
for (const element of elements) {
element.addEventListener("submit", function (e) {
e.preventDefault();
@ -17,15 +16,33 @@ window.addEventListener("DOMContentLoaded", () => {
"confirmDoneJS can only be executed on an exising form"
);
}
const event = e as SubmitEvent;
let submitter: HTMLButtonElement | null = null;
if (event.submitter) {
submitter = event.submitter as HTMLButtonElement;
} else {
submitter = document.activeElement as HTMLButtonElement;
}
const tickets_number = submitter?.dataset.tickets ?? "0";
const invoices_number = submitter?.dataset.invoices ?? "0";
const message = `Toutes les données vont être perdues.
<br>Tickets concernés : ${tickets_number}
Devis concernées : ${invoices_number}`;
sweetalert.methods.confirm(
"Êtes-vous sûr ?",
"Toutes les données vont être perdu",
message,
el as HTMLFormElement,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function (response: SweetAlertResult<any>) {
(el as HTMLFormElement).submit();
},
{ icon: "warning" }
{ icon: "warning" },
true
);
return false;
});

View file

@ -11,7 +11,8 @@ export default {
text: string = "",
self: Object,
after: (response: SweetAlertResult<any>) => void = () => {},
options: SweetAlertOptions = {}
options: SweetAlertOptions = {},
html: Boolean = false
) {
if (!self) {
throw Error("self is needed to set \"this\" on callback");
@ -19,10 +20,12 @@ export default {
if (!after) {
throw Error("callback is needed when using confirm");
}
const icon = options.icon ?? "warning";
Swal.fire({
// define options to fire
const saOptions = {
title: title,
text: text,
color: getComputedStyle(document.body).getPropertyValue("--bs-body-color"),
icon: icon,
showCancelButton: true,
@ -40,7 +43,18 @@ export default {
},
buttonsStyling: false,
background: getComputedStyle(document.body).getPropertyValue("--bs-body-bg"),
}).then((result) => {
};
// Enable HTML content interpretation in the modal options
let message = {};
if (html) {
message = {html: text};
} else {
message = { text: text };
}
const fullOptions = {...saOptions, ...message};
Swal.fire(fullOptions).then((result) => {
if (result.isConfirmed) {
if (after) {
after.apply(self, [result]);

View file

@ -53,7 +53,8 @@
<td class="text-center align-middle"><span class="badge bg-info">{{ $invoiceModel->sent_client_date }}</span></td>
<td class="text-center align-middle"><span class="badge bg-info">{{ $invoiceModel->signed_date }}</span></td>
<td class="text-center align-middle"><span class="badge bg-info">{{ $invoiceModel->sent_accounting_date }}</span></td>
<td class="text-center align-middle">{{ $invoiceModel->site->name }}</td>
<td class="text-center align-middle">
{{ $invoiceModel->site->name }}
<td class="text-center align-middle"><span class="badge bg-info">{{ $invoiceModel->updated_at }}</span></td>
<td class="text-end align-middle">
<div class="btn-group">

View file

@ -78,6 +78,8 @@
@method('DELETE')
<button type="submit"
class="btn btn-sm btn-danger"
data-tickets="{{ $siteModel->tickets()->count() }}"
data-invoices="{{ $siteModel->invoices()->count() }}"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.actions.delete') }}">

View file

@ -18,10 +18,11 @@
</h1>
@if (session('tickets.pr'))
@php
$projectManager = \App\Models\Site::query()->find(session('tickets.pr'))->projectManager;
$site = \App\Models\Site::query()->find(session('tickets.pr'));
$projectManager = $site ? $site->projectManager : false;
@endphp
@if($projectManager)
<h2>{{ __("Chef de projet") }} : {{ $projectManager->name }}</h2>
@if ($projectManager)
<h2>{{ __('Chef de projet') }} : {{ $projectManager->name }}</h2>
@endif
@endif
</div>
@ -76,8 +77,7 @@
{{-- All types --}}
<div class="col">
<div class="input-group">
<label class="input-group-text" for="selectType"><i
class="fa-solid fa-ticket"></i></label>
<label class="input-group-text" for="selectType"><i class="fa-solid fa-ticket"></i></label>
<select class="form-select" id="selectType">
<option value="-1">{{ __('Tous les types') }}</option>
@foreach ($types as $type)
@ -100,8 +100,7 @@
{{-- All status --}}
<div class="col">
<div class="input-group">
<label class="input-group-text" for="selectStatus"><i
class="fa-solid fa-sign-hanging"></i></label>
<label class="input-group-text" for="selectStatus"><i class="fa-solid fa-sign-hanging"></i></label>
<select class="form-select" id="selectStatus">
<option value="-1">{{ __('Tous les statuts') }}</option>
@foreach ($status as $statut)
@ -125,8 +124,7 @@
@can('assignTickets', App\Models\Ticket::class)
<div class="col">
<div class="input-group">
<label class="input-group-text" for="selectAssign"><i
class="fa-solid fa-hat-wizard"></i></label>
<label class="input-group-text" for="selectAssign"><i class="fa-solid fa-hat-wizard"></i></label>
<select class="form-select" id="selectAssign">
<option value="-1">{{ __("Sans filtre d'assignation") }}</option>
@foreach (\App\Models\User::adminUsers() as $user)
@ -149,8 +147,7 @@
@endcan
{{-- Search form --}}
<div class="col">
<form id="searchTicketForm"
action="{{ request()->fullUrlWithQuery(['search' => '']) }}">
<form id="searchTicketForm" action="{{ request()->fullUrlWithQuery(['search' => '']) }}">
<div class="input-group mb-3">
<input class="form-control" id="searchTicket" name="search" type="text"
value="{{ session()->get('tickets.search') }}"
@ -179,8 +176,7 @@
<div class="btn-group float-end">
@if (session('tickets.pr') && session('tickets.st') != \App\Enums\Tickets\TicketStatusEnum::archived->value)
@if (session('tickets.or'))
<a class="btn btn-info float-end"
href="{{ route('bo.ticket.index', ['ord' => -1]) }}">
<a class="btn btn-info float-end" href="{{ route('bo.ticket.index', ['ord' => -1]) }}">
<i
class="fa-solid fa-equals"></i>&nbsp;&nbsp;{{ __('see_mode', ['mode' => __('validation.custom.default')]) }}
</a>
@ -209,7 +205,7 @@
</div>
</div>
<div class="card-body">
<div class="overflow-scroll w-100">
<div class="w-100 overflow-scroll">
<table class="table-hover table">
<thead>
@php
@ -245,16 +241,13 @@
@endphp
@include('bo.modules.table-col-sorter', [
'cols' => $cols,
'ignore' => session('tickets.or')
? array_keys($cols)
: ['id', 'site', 'status', 'tickets'],
'ignore' => session('tickets.or') ? array_keys($cols) : ['id', 'site', 'status', 'tickets'],
])
</thead>
<tbody>
@if (!count($tickets))
<tr>
<td class="text-center"
colspan="@if (Auth::user()->isAdmin()) 9 @else 6 @endif">
<td class="text-center" colspan="@if (Auth::user()->isAdmin()) 9 @else 6 @endif">
<h2>{{ __('Aucun ticket') }}</h2>
</td>
</tr>
@ -267,7 +260,7 @@
'routeName' => 'ticket',
'model' => $ticket,
'models' => $tickets,
'value' => ($key + 1) + (($tickets->currentPage() - 1) * $tickets->perPage()),
'value' => $key + 1 + ($tickets->currentPage() - 1) * $tickets->perPage(),
'noRightsVisible' => false,
])
@endif
@ -291,16 +284,14 @@
'sitename' => $ticket->site->name,
]) }}">{{ $ticket->site->name }}</a>
@else
<span
class="text-decoration-underline">{{ $ticket->site->name }}</span>
<span class="text-decoration-underline">{{ $ticket->site->name }}</span>
@endif
</div>
</td>
@endif
{{-- type --}}
<td class="text-center">
<span
class="badge {{ $ticket->type->class() }}">{{ $ticket->type->label() }}</span>
<span class="badge {{ $ticket->type->class() }}">{{ $ticket->type->label() }}</span>
</td>
{{-- status --}}
<td scope="row">
@ -347,13 +338,10 @@
$ticket_files = $ticket->getFiles();
@endphp
@foreach ($ticket_files as $key => $file)
@if (
$file !== null &&
(\App\Lib\Helpers\ToolboxHelper::isImageFile($file) || \App\Lib\Helpers\ToolboxHelper::isPdfFile($file)))
@if ($file !== null && (\App\Lib\Helpers\ToolboxHelper::isImageFile($file) || \App\Lib\Helpers\ToolboxHelper::isPdfFile($file)))
<a class="glightbox btn btn-outline-dark"
data-gallery="gallery{{ $ticket->id }}"
href="{{ asset($file) }}" target="_blank"><i
class="fa-solid fa-file-image"></i>
data-gallery="gallery{{ $ticket->id }}" href="{{ asset($file) }}"
target="_blank"><i class="fa-solid fa-file-image"></i>
</a>
@break
@endif
@ -361,8 +349,8 @@
{{-- Modal du ticket --}}
<!-- Button trigger modal -->
<button class="btn btn-outline-info" data-bs-toggle="modal"
data-bs-target="#ticket{{ $ticket->id }}Modal"
type="button"><i class="fa-solid fa-eye"></i></button>
data-bs-target="#ticket{{ $ticket->id }}Modal" type="button"><i
class="fa-solid fa-eye"></i></button>
<a class="btn btn-outline-success"
href="{{ route('bo.ticket.show', $ticket->id) }}"><i
class="fa-solid fa-comment"></i></a>
@ -377,10 +365,8 @@
action="{{ route('bo.ticket.archive', $ticket) }}">
@method('PUT')
@csrf
<button
class="btn btn-outline-danger rounded-0 rounded-end"
type="submit"><i
class="fa-solid fa-box-archive"></i></button>
<button class="btn btn-outline-danger rounded-0 rounded-end"
type="submit"><i class="fa-solid fa-box-archive"></i></button>
</form>
@endif
@endcan
@ -388,13 +374,12 @@
</div>
<!-- Modal -->
<div class="modal fade" id="ticket{{ $ticket->id }}Modal"
aria-labelledby="ticket{{ $ticket->id }}ModalLabel"
aria-hidden="true" tabindex="-1">
aria-labelledby="ticket{{ $ticket->id }}ModalLabel" aria-hidden="true"
tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"
id="ticket{{ $ticket->id }}ModalLabel">
<h5 class="modal-title" id="ticket{{ $ticket->id }}ModalLabel">
@can('isAdmin')
<a href="{{ route('bo.sites.show', $ticket->site) }}"
title="{{ __('Voir le site :sitename', [
@ -413,9 +398,8 @@
{{ $ticket->type->label() }}
</span>
</h5>
<button class="btn btn-sm btn-primary bg-primary"
data-bs-dismiss="modal" type="button"
aria-label="Close">
<button class="btn btn-sm btn-primary bg-primary" data-bs-dismiss="modal"
type="button" aria-label="Close">
<i class="fa-solid fa-xmark text-white"></i>
</button>
</div>
@ -431,8 +415,7 @@
<p>{!! clean(nl2br($ticket->text)) !!}</p>
</div>
<div class="modal-footer">
<button class="btn btn-sm btn-primary"
data-bs-dismiss="modal"
<button class="btn btn-sm btn-primary" data-bs-dismiss="modal"
type="button">{{ __('Fermer') }}</button>
</div>
</div>