diff --git a/app/Http/Controllers/Bo/TicketController.php b/app/Http/Controllers/Bo/TicketController.php
index 9c21e39..b1fe4ff 100644
--- a/app/Http/Controllers/Bo/TicketController.php
+++ b/app/Http/Controllers/Bo/TicketController.php
@@ -13,8 +13,10 @@ use App\Http\Requests\Bo\ChangeTicketStatus;
use App\Http\Requests\Bo\CreateMessage;
use App\Http\Requests\Bo\SortTickets;
use App\Http\Requests\Bo\StoreTicket;
+use App\Http\Requests\Bo\UpdateChangeOrderRequest;
use App\Http\Requests\Bo\UpdateMessage;
use App\Http\Requests\Bo\UpdateTicket;
+use App\Http\Requests\Bo\LinkTicket;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Mail\NewTicket;
@@ -29,6 +31,7 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
+use Illuminate\Support\Arr;
class TicketController extends Controller
{
@@ -74,7 +77,6 @@ class TicketController extends Controller
}));
} //end if
-
/** @var \App\Models\User */
$user = auth()->user();
$tickets = Ticket::with(['site', 'labels']);
@@ -97,6 +99,10 @@ class TicketController extends Controller
$tickets = $this->filterTickets($tickets, $request);
$tickets = $this->searchTicket($tickets, $request);
+
+ /** Sort columns with a query */
+ $this->sortQuery($tickets);
+
/** Custom pagination */
$tickets = $this->customPaginate($tickets, $request->pagination);
@@ -134,10 +140,13 @@ class TicketController extends Controller
abort(403);
}
+ $linkedTicket = $ticket->linkedTicket()->get();
+
$messages = $ticket->messages()->orderBy('created_at', 'DESC')->get();
$isSubscribed = (bool)$ticket->subscriptions()->where('user_id', $user->id)->count();
return view('bo.pages.tickets.show', [
'ticket' => $ticket,
+ 'linkedTicket' => $linkedTicket,
'messages' => $messages,
'isSubscribed' => $isSubscribed
]);
@@ -484,6 +493,48 @@ class TicketController extends Controller
return back()->with('info', trans('Le ticket n\'est plus assigné à personne'));
}
+
+ /**
+ * Link two tickets.
+ *
+ * @param LinkTicket $request
+ * @param Ticket $ticket
+ *
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function linkTicket(LinkTicket $request, Ticket $ticket)
+ {
+ $linkedTicket = Ticket::findOrFail($request->validated())->first();
+
+ if ($ticket->site_id !== $linkedTicket->site_id) {
+ 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);
+ return back()->with('success', trans('Les tickets ont été liés avec succès.'));
+ }
+
+ return back()->with('error', trans('Les tickets sont déjà liés ensemble.'));
+ }
+
+ /**
+ * Unlink two tickets.
+ *
+ * @param Ticket $ticket
+ * @param Ticket $linkedTicket
+ *
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function unlinkTicket(Ticket $ticket, Ticket $linkedTicket)
+ {
+ $ticket->linkedTicket()->detach($linkedTicket->id);
+ $linkedTicket->linkedTicket()->detach($ticket->id);
+
+ return back()->with('warning', trans('Le lien entre les tickets à été supprimé.'));
+ }
+
// * PRIVATES
/**
@@ -502,8 +553,7 @@ class TicketController extends Controller
Session::remove('tickets.st');
Session::remove('tickets.pr');
Session::remove('tickets.ty');
- Session::remove('tickets.sort_col');
- Session::remove('tickets.sort_way');
+ Session::remove('tickets.or');
Session::remove('tickets.search');
}
if ($request->assr) {
@@ -518,17 +568,15 @@ class TicketController extends Controller
if ($request->tys) {
Session::remove('tickets.ty');
}
- if ($request->sort_col) {
- Session::put('tickets.sort_col', $request->sort_col);
- }
- if ($request->sort_way) {
- Session::put('tickets.sort_way', $request->sort_way);
+ if ($request->ord) {
+ Session::remove('tickets.or');
}
if ($request->pr) {
Session::put('tickets.pr', $request->pr);
}
if ($request->st) {
Session::put('tickets.st', $request->st);
+ Session::remove('tickets.or');
}
if ($request->ty) {
Session::put('tickets.ty', $request->ty);
@@ -536,6 +584,9 @@ class TicketController extends Controller
if ($request->ass) {
Session::put('tickets.ass', $request->ass);
}
+ if ($request->or && Session::has('tickets.pr')) {
+ Session::put('tickets.or', $request->or);
+ }
Session::save();
if (Session::has('tickets.pr')) {
@@ -556,27 +607,12 @@ class TicketController extends Controller
$tickets->where('type', Session::get('tickets.ty'));
}
-
- if (
- Session::has('tickets.sort_col') and
- Session::has('tickets.sort_way') and
- in_array(Session::get('tickets.sort_col'), [
- 'id',
- 'site_id',
- 'created_at',
- 'created_by',
- 'type',
- 'status',
- 'assigned_to'
- ])
- ) {
- $tickets->orderBy(
- Session::get('tickets.sort_col'),
- Session::get('tickets.sort_way')
- );
+ if (Session::has('tickets.or')) {
+ $tickets->orderBy('order', 'ASC');
} else {
$tickets->orderBy('last_activity', 'DESC');
}
+
return $tickets;
}
@@ -733,4 +769,62 @@ class TicketController extends Controller
}
return \sprintf("
Fichier %d :
%s
", $fileNb, $file);
}
+
+ /**
+ * Change order.
+ *
+ * @param \App\Http\Requests\Bo\UpdateChangeOrderRequest $request
+ * @param mixed ...$params
+ * @return \Illuminate\Http\RedirectResponse
+ * @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au params utilisé.
+ */
+ public function changeOrder(UpdateChangeOrderRequest $request, mixed ...$params): \Illuminate\Http\RedirectResponse
+ {
+ $paramNames = collect(request()->route()->parameterNames);
+ $className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly();
+ $className = "\\App\\Models\\{$className}";
+ $model = Arr::get($params, $paramNames->count() - 2);
+
+ if (!class_exists($className)) {
+ throw new \RuntimeException("The {$className} class doesn't exist.");
+ }
+
+ if (!\is_object($model)) {
+ throw new \RuntimeException("The second last param isn't an object.");
+ }
+
+ if (get_class($model) !== (string)Str::of($className)->replaceStart('\\', '')) {
+ throw new \RuntimeException("The {$className} doesn't match second last binded param.");
+ }
+
+ /** @var \Illuminate\Database\Eloquent\Model|null */
+ $model = $className::query()->where($model->getRouteKeyName(), $model->getRouteKey())->first();
+
+ if (!$model) {
+ return back()->with('error', trans('crud.messages.order_not_changed'));
+ }
+
+ return DB::transaction(function () use ($request, $className, $model) {
+ /** @var \Illuminate\Support\Collection $models */
+ $models = $className::query()
+ ->orderBy('order', 'ASC')
+ ->where('site_id', $model->site_id)
+ ->where('status', '!=', TicketStatusEnum::archived)
+ ->where('id', '!=', $model->getKey())
+ ->where('order', $request->direction ? '<' : '>', $model->order)
+ ->get();
+ /** @var \Illuminate\Database\Eloquent\Model|null $tmp */
+ $tmp = $request->direction ? $models->last() : $models->first();
+
+ if (!$tmp) {
+ return back()->with('error', trans('crud.messages.order_not_changed'));
+ }
+
+ $newOrder = $tmp->getOriginal('order');
+ $tmp->update(['order' => $model->getOriginal('order')]);
+ $model->update(['order' => $newOrder]);
+
+ return redirect()->back()->with('success', trans('crud.messages.order_changed'));
+ });
+ }
}
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index ed2f0d8..5686abc 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -223,10 +223,6 @@ class Controller extends BaseController
) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
}
- } elseif (Schema::hasColumn($table, 'order')) {
- $query = $query->orderBy('order', 'ASC');
- Session::put("$rName.sort_col", 'order');
- Session::put("$rName.sort_way", 'asc');
}
return $query;
}
diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php
index 7b41ae2..7b691f9 100644
--- a/app/Http/Middleware/RedirectIfAuthenticated.php
+++ b/app/Http/Middleware/RedirectIfAuthenticated.php
@@ -27,7 +27,7 @@ class RedirectIfAuthenticated
if (Auth::guard($guard)->check()) {
switch ($guard) {
case 'backend':
- if (!$request->routeIs('bo.*')) {
+ if (!$request->routeIs('bo.*') && !$request->routeIs('login')) {
continue;
}
return \redirect()->route('bo.dashboard');
diff --git a/app/Http/Requests/Bo/LinkTicket.php b/app/Http/Requests/Bo/LinkTicket.php
new file mode 100644
index 0000000..3ce7d4e
--- /dev/null
+++ b/app/Http/Requests/Bo/LinkTicket.php
@@ -0,0 +1,36 @@
+check();
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ $ticket = $this->route('ticket');
+ return [
+ 'linked_ticket_id' => [
+ 'required',
+ 'numeric',
+ 'exists:tickets,id',
+ 'not_in:' . $ticket->id,
+ ]
+ ];
+ }
+}
diff --git a/app/Http/Requests/Bo/StoreTicket.php b/app/Http/Requests/Bo/StoreTicket.php
index 41a512c..c85fcc4 100644
--- a/app/Http/Requests/Bo/StoreTicket.php
+++ b/app/Http/Requests/Bo/StoreTicket.php
@@ -96,6 +96,7 @@ class StoreTicket extends FormRequest
}
}
],
+ 'date_mep' => 'required|date',
'text' => 'required|string|min:40',
'title' => 'required|string|between:3,255',
'file_tickets' => 'sometimes|array',
diff --git a/app/Http/Requests/Bo/UpdateChangeOrderRequest.php b/app/Http/Requests/Bo/UpdateChangeOrderRequest.php
new file mode 100644
index 0000000..d4829c7
--- /dev/null
+++ b/app/Http/Requests/Bo/UpdateChangeOrderRequest.php
@@ -0,0 +1,43 @@
+check();
+ }
+
+ /**
+ * Prepare the data for validation.
+ *
+ * @return void
+ */
+ protected function prepareForValidation(): void
+ {
+ $this->merge([
+ 'direction' => $this->route('direction') === 'up' ? true : false
+ ]);
+ }
+
+ /**
+ * Get the validation rules that apply to the request.
+ *
+ * @return array
+ */
+ public function rules(): array
+ {
+ return [
+ 'direction' => 'required|boolean',
+ ];
+ }
+
+}
diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php
index 460ef6d..c56977d 100644
--- a/app/Models/Ticket.php
+++ b/app/Models/Ticket.php
@@ -32,6 +32,8 @@ class Ticket extends Model
'site_id',
'type',
'status',
+ 'order',
+ 'date_mep',
'text',
'title',
'is_internal',
@@ -47,6 +49,7 @@ class Ticket extends Model
'is_internal' => 'bool',
'type' => TicketTypeEnum::class,
'status' => TicketStatusEnum::class,
+ 'date_mep' => 'date',
'last_activity' => 'timestamp',
];
@@ -59,6 +62,7 @@ class Ticket extends Model
{
static::creating(function (Ticket $ticket) {
// If bug force the ticket status on new. All the others are estimateTodo.
+ self::setOrder($ticket);
if ($ticket->type->equals(TicketTypeEnum::bug)) {
$ticket->status = TicketStatusEnum::new;
} else {
@@ -66,7 +70,7 @@ class Ticket extends Model
}
});
static::updating(function (Ticket $ticket) {
- if (!$ticket->original['status'] !== $ticket->status->value) {
+ if (!$ticket->original['status'] !== $ticket->status->value && $ticket->original['order'] === $ticket->order) {
event(new TicketStatusChanged(
$ticket,
TicketStatusEnum::make($ticket->original['status']),
@@ -85,9 +89,25 @@ class Ticket extends Model
$ticket->status->label()
)));
}
+ if ($ticket->status->equals(TicketStatusEnum::archived)) {
+ self::setOrder($ticket, true);
+ }
});
}
+ /**
+ * Set order after the last element of the list.
+ *
+ * @param self $model
+ * @param boolean $isArchived
+ * @return void
+ */
+ private static function setOrder(self $model, bool $isArchived = false): void
+ {
+ $model->order = \intval(self::query()->where('status', $isArchived ? '=' : '!=', TicketStatusEnum::archived)->max('order')) + 1;
+ }
+
+
// * RELATIONSHIPS
/**
@@ -173,6 +193,16 @@ class Ticket extends Model
return $this->belongsTo(TicketType::class, 'type', 'type');
}
+ /**
+ * BelongsToMany Linked Ticket
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function linkedTicket()
+ {
+ return $this->belongsToMany(Ticket::class, 'linked_tickets', 'ticket_id', 'linked_ticket_id');
+ }
+
// * METHODS
/**
diff --git a/app/Policies/TicketPolicy.php b/app/Policies/TicketPolicy.php
index bcb8074..14ede6f 100644
--- a/app/Policies/TicketPolicy.php
+++ b/app/Policies/TicketPolicy.php
@@ -67,6 +67,17 @@ class TicketPolicy
return $user->isAdmin();
}
+ /**
+ * Determine if the user can link ticket .
+ *
+ * @param \App\Models\User $user
+ * @return boolean
+ */
+ public function linkTicket(User $user)
+ {
+ return $user->isAdmin();
+ }
+
/**
* Determine if the user can change the internal status of a given ticket.
*
diff --git a/app/Traits/Controllers/ChangesModelOrder.php b/app/Traits/Controllers/ChangesModelOrder.php
new file mode 100644
index 0000000..f504823
--- /dev/null
+++ b/app/Traits/Controllers/ChangesModelOrder.php
@@ -0,0 +1,64 @@
+route()->parameterNames);
+ $className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly();
+ $className = "\\App\\Models\\{$className}";
+ $model = Arr::get($params, $paramNames->count() - 2);
+
+ if (!class_exists($className)) {
+ throw new \RuntimeException("The {$className} class doesn't exist.");
+ }
+
+ if (!\is_object($model)) {
+ throw new \RuntimeException("The second last param isn't an object.");
+ }
+
+ if (get_class($model) !== (string)Str::of($className)->replaceStart('\\', '')) {
+ throw new \RuntimeException("The {$className} doesn't match second last binded param.");
+ }
+
+ /** @var \Illuminate\Database\Eloquent\Model|null */
+ $model = $className::query()->where($model->getRouteKeyName(), $model->getRouteKey())->first();
+
+ if (!$model) {
+ return back()->with('error', trans('crud.messages.order_not_changed'));
+ }
+
+ return DB::transaction(function () use ($request, $className, $model) {
+ /** @var \Illuminate\Database\Eloquent\Model|null */
+ $tmp = $className::query()
+ ->where('order', $request->direction ? '<' : '>', $model->getOriginal('order'))
+ ->orderBy('order', $request->direction ? 'DESC' : 'ASC')
+ ->first();
+
+ if (!$tmp) {
+ return back()->with('error', trans('crud.messages.order_not_changed'));
+ }
+
+ $newOrder = $tmp->getOriginal('order');
+ $tmp->update(['order' => $model->getOriginal('order')]);
+ $model->update(['order' => $newOrder]);
+
+ return redirect()->back()->with('success', trans('crud.messages.order_changed'));
+ });
+ }
+}
diff --git a/database/migrations/2025_03_12_102925_change_order_to_tickets.php b/database/migrations/2025_03_12_102925_change_order_to_tickets.php
new file mode 100644
index 0000000..3483b0a
--- /dev/null
+++ b/database/migrations/2025_03_12_102925_change_order_to_tickets.php
@@ -0,0 +1,40 @@
+integer('order')->after('status')->comment('Ticket order.');
+ });
+
+ Site::query()->get()->map(function ($site) {
+ $site->tickets->map(function ($ticket, $ticketIndex) {
+ $ticket->order = $ticketIndex + 1;
+ $ticket->saveQuietly();
+ });
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down(): void
+ {
+ Schema::table('tickets', function (Blueprint $table) {
+ $table->dropColumn('order');
+ });
+ }
+};
diff --git a/database/migrations/2025_03_13_094420_add_mep_date_on_tickets.php b/database/migrations/2025_03_13_094420_add_mep_date_on_tickets.php
new file mode 100644
index 0000000..deb9c5b
--- /dev/null
+++ b/database/migrations/2025_03_13_094420_add_mep_date_on_tickets.php
@@ -0,0 +1,32 @@
+date('date_mep')->nullable()->after('gitlab_issue')->comment('Date MEP souhaitée');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down(): void
+ {
+ Schema::table('tickets', function (Blueprint $table) {
+ $table->dropColumn('date_mep');
+ });
+ }
+};
diff --git a/database/migrations/2025_03_13_151503_tickets_linking.php b/database/migrations/2025_03_13_151503_tickets_linking.php
new file mode 100644
index 0000000..801bbb3
--- /dev/null
+++ b/database/migrations/2025_03_13_151503_tickets_linking.php
@@ -0,0 +1,35 @@
+unsignedBigInteger('ticket_id')
+ ->constrained()->cascadeOnDelete();
+ $table->unsignedBigInteger('linked_ticket_id')
+ ->constrained()->cascadeOnDelete();
+ $table->foreign('ticket_id')->references('id')->on('tickets');
+ $table->foreign('linked_ticket_id')->references('id')->on('tickets');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('linked_tickets');
+ }
+};
diff --git a/resources/lang/en.json b/resources/lang/en.json
index d208ff2..714172c 100644
--- a/resources/lang/en.json
+++ b/resources/lang/en.json
@@ -50,5 +50,6 @@
"Verify Your Email Address": "Verify Your Email Address",
"Whoops!": "Whoops!",
"You are logged in!": "You are logged in!",
- "You are receiving this email because we received a password reset request for your account.": "You are receiving this email because we received a password reset request for your account."
-}
\ No newline at end of file
+ "You are receiving this email because we received a password reset request for your account.": "You are receiving this email because we received a password reset request for your account.",
+ "see_mode": "see :mode mode"
+}
diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php
index 2076bd3..35c2795 100644
--- a/resources/lang/en/validation.php
+++ b/resources/lang/en/validation.php
@@ -214,4 +214,14 @@ return [
'username' => 'username',
'year' => 'year',
],
+ 'custom' => [
+ 'type' => 'type',
+ 'tickets' => 'tickets',
+ 'assigned_to' => 'assigned to',
+ 'order' => 'order',
+ 'priority' => 'priority',
+ 'default' => 'default',
+ 'date_mep' => 'desired MEP date',
+ 'date_mep_empty' => 'no date entered'
+ ]
];
diff --git a/resources/lang/fr.json b/resources/lang/fr.json
index 9c2ad17..3549abe 100644
--- a/resources/lang/fr.json
+++ b/resources/lang/fr.json
@@ -55,5 +55,6 @@
"Verify Your Email Address": "Vérifiez votre adresse e-mail",
"Whoops!": "Oups !",
"You are logged in!": "Vous êtes connecté·e !",
- "You are receiving this email because we received a password reset request for your account.": "Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte."
-}
\ No newline at end of file
+ "You are receiving this email because we received a password reset request for your account.": "Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte.",
+ "see_mode": "voir mode :mode"
+}
diff --git a/resources/lang/fr/crud.php b/resources/lang/fr/crud.php
index ddb1331..d219d12 100644
--- a/resources/lang/fr/crud.php
+++ b/resources/lang/fr/crud.php
@@ -89,5 +89,7 @@ return [
'sort_delete' => 'Supprimer le tri',
'sort_arrow' => 'Ici, les flèches sont utilisées pour trier et non pour filtrer',
'right' => 'Vous n\'avez pas les droits',
+ 'view_linked_ticket' => 'voir le ticket :model',
+ 'unlink_ticket' => 'supprimer le lien avec le ticket :model'
]
];
diff --git a/resources/lang/fr/validation-inline.php b/resources/lang/fr/validation-inline.php
deleted file mode 100644
index a06110a..0000000
--- a/resources/lang/fr/validation-inline.php
+++ /dev/null
@@ -1,131 +0,0 @@
- 'Ce champ doit être accepté.',
- 'active_url' => 'Ce n\'est pas une URL valide',
- 'after' => 'La date doit être postérieure au :date.',
- 'after_or_equal' => 'La date doit être postérieure ou égale au :date.',
- 'alpha' => 'Ce champ doit contenir uniquement des lettres',
- 'alpha_dash' => 'Ce champ doit contenir uniquement des lettres, des chiffres et des tirets.',
- 'alpha_num' => 'Ce champ doit contenir uniquement des chiffres et des lettres.',
- 'array' => 'Ce champ doit être un tableau.',
- 'attached' => 'Ce champ est déjà attaché.',
- 'before' => 'Ce champ doit être une date antérieure au :date.',
- 'before_or_equal' => 'Ce champ doit être une date antérieure ou égale au :date.',
- 'between' => [
- 'array' => 'Le tableau doit contenir entre :min et :max éléments.',
- 'file' => 'La taille du fichier doit être comprise entre :min et :max kilo-octets.',
- 'numeric' => 'La valeur doit être comprise entre :min et :max.',
- 'string' => 'Le texte doit contenir entre :min et :max caractères.',
- ],
- 'boolean' => 'Ce champ doit être vrai ou faux.',
- 'confirmed' => 'Le champ de confirmation ne correspond pas.',
- 'date' => 'Ce n\'est pas une date valide.',
- 'date_equals' => 'La date doit être égale à :date.',
- 'date_format' => 'Ce champ ne correspond pas au format :format.',
- 'different' => 'Cette valeur doit être différente de :other.',
- 'digits' => 'Ce champ doit contenir :digits chiffres.',
- 'digits_between' => 'Ce champ doit contenir entre :min et :max chiffres.',
- 'dimensions' => 'La taille de l\'image n\'est pas conforme.',
- 'distinct' => 'Ce champ a une valeur en double.',
- 'email' => 'Ce champ doit être une adresse email valide.',
- 'ends_with' => 'Ce champ doit se terminer par une des valeurs suivantes : :values',
- 'exists' => 'Ce champ sélectionné est invalide.',
- 'file' => 'Ce champ doit être un fichier.',
- 'filled' => 'Ce champ doit avoir une valeur.',
- 'gt' => [
- 'array' => 'Le tableau doit contenir plus de :value éléments.',
- 'file' => 'La taille du fichier doit être supérieure à :value kilo-octets.',
- 'numeric' => 'La valeur doit être supérieure à :value.',
- 'string' => 'Le texte doit contenir plus de :value caractères.',
- ],
- 'gte' => [
- 'array' => 'Le tableau doit contenir au moins :value éléments.',
- 'file' => 'La taille du fichier doit être supérieure ou égale à :value kilo-octets.',
- 'numeric' => 'La valeur doit être supérieure ou égale à :value.',
- 'string' => 'Le texte doit contenir au moins :value caractères.',
- ],
- 'image' => 'Ce champ doit être une image.',
- 'in' => 'Ce champ est invalide.',
- 'in_array' => 'Ce champ n\'existe pas dans :other.',
- 'integer' => 'Ce champ doit être un entier.',
- 'ip' => 'Ce champ doit être une adresse IP valide.',
- 'ipv4' => 'Ce champ doit être une adresse IPv4 valide.',
- 'ipv6' => 'Ce champ doit être une adresse IPv6 valide.',
- 'json' => 'Ce champ doit être un document JSON valide.',
- 'lt' => [
- 'array' => 'Le tableau doit contenir moins de :value éléments.',
- 'file' => 'La taille du fichier doit être inférieure à :value kilo-octets.',
- 'numeric' => 'La valeur doit être inférieure à :value.',
- 'string' => 'Le texte doit contenir moins de :value caractères.',
- ],
- 'lte' => [
- 'array' => 'Le tableau doit contenir au plus :value éléments.',
- 'file' => 'La taille du fichier doit être inférieure ou égale à :value kilo-octets.',
- 'numeric' => 'La valeur doit être inférieure ou égale à :value.',
- 'string' => 'Le texte doit contenir au plus :value caractères.',
- ],
- 'max' => [
- 'array' => 'Le tableau ne peut contenir plus de :max éléments.',
- 'file' => 'La taille du fichier ne peut pas dépasser :max kilo-octets.',
- 'numeric' => 'La valeur ne peut être supérieure à :max.',
- 'string' => 'Le texte ne peut contenir plus de :max caractères.',
- ],
- 'mimes' => 'Le fichier doit être de type : :values.',
- 'mimetypes' => 'Le fichier doit être de type : :values.',
- 'min' => [
- 'array' => 'Le tableau doit contenir au moins :min éléments.',
- 'file' => 'La taille du fichier doit être supérieure à :min kilo-octets.',
- 'numeric' => 'La valeur doit être supérieure ou égale à :min.',
- 'string' => 'Le texte doit contenir au moins :min caractères.',
- ],
- 'multiple_of' => 'La valeur doit être un multiple de :value',
- 'not_in' => 'Le champ sélectionné n\'est pas valide.',
- 'not_regex' => 'Le format du champ n\'est pas valide.',
- 'numeric' => 'Ce champ doit contenir un nombre.',
- 'password' => 'Le mot de passe est incorrect',
- 'present' => 'Ce champ doit être présent.',
- 'prohibited' => 'Ce champ est interdit',
- 'prohibited_if' => 'Ce champ est interdit quand :other a la valeur :value.',
- 'prohibited_unless' => 'Ce champ est interdit à moins que :other ait l\'une des valeurs :values.',
- 'regex' => 'Le format du champ est invalide.',
- 'relatable' => 'Ce champ n\'est sans doute pas associé avec cette donnée.',
- 'required' => 'Ce champ est obligatoire.',
- 'required_if' => 'Ce champ est obligatoire quand la valeur de :other est :value.',
- 'required_unless' => 'Ce champ est obligatoire sauf si :other est :values.',
- 'required_with' => 'Ce champ est obligatoire quand :values est présent.',
- 'required_with_all' => 'Ce champ est obligatoire quand :values sont présents.',
- 'required_without' => 'Ce champ est obligatoire quand :values n\'est pas présent.',
- 'required_without_all' => 'Ce champ est requis quand aucun de :values n\'est présent.',
- 'same' => 'Ce champ doit être identique à :other.',
- 'size' => [
- 'array' => 'Le tableau doit contenir :size éléments.',
- 'file' => 'La taille du fichier doit être de :size kilo-octets.',
- 'numeric' => 'La valeur doit être :size.',
- 'string' => 'Le texte doit contenir :size caractères.',
- ],
- 'starts_with' => 'Ce champ doit commencer avec une des valeurs suivantes : :values',
- 'string' => 'Ce champ doit être une chaîne de caractères.',
- 'timezone' => 'Ce champ doit être un fuseau horaire valide.',
- 'unique' => 'La valeur est déjà utilisée.',
- 'uploaded' => 'Le fichier n\'a pu être téléversé.',
- 'url' => 'Le format de l\'URL n\'est pas valide.',
- 'uuid' => 'Ce champ doit être un UUID valide',
- 'custom' => [
- 'attribute-name' => [
- 'rule-name' => 'custom-message',
- ],
- ],
- 'attributes' => [],
-];
diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php
index 21876b4..e50b509 100644
--- a/resources/lang/fr/validation.php
+++ b/resources/lang/fr/validation.php
@@ -248,5 +248,16 @@ return [
'url_re7' => 'Url de RECETTE',
'username' => 'nom d\'utilisateur',
'year' => 'année',
+ 'id' => 'identifiant'
],
+ 'custom' => [
+ 'type' => 'type',
+ 'tickets' => 'étiquettes',
+ 'assigned_to' => 'assigné à',
+ 'order' => 'ordre',
+ 'priority' => 'priorité',
+ 'default' => 'défaut',
+ 'date_mep' => 'date de MEP souhaitée',
+ 'date_mep_empty' => 'aucune date renseignée'
+ ]
];
diff --git a/resources/ts/bo/utilities/fontawesome.ts b/resources/ts/bo/utilities/fontawesome.ts
index 836339f..f3568c8 100644
--- a/resources/ts/bo/utilities/fontawesome.ts
+++ b/resources/ts/bo/utilities/fontawesome.ts
@@ -33,6 +33,7 @@ import {
faDeleteLeft,
faDiagramProject,
faDownload,
+ faEquals,
faEraser,
faEye,
faEyeSlash,
@@ -60,10 +61,12 @@ import {
faTicket,
faTimes,
faTrash,
+ faTriangleExclamation,
faUndoAlt,
faUsers,
faWandMagic,
- faXmark
+ faXmark,
+ faLinkSlash
} from "@fortawesome/free-solid-svg-icons";
config.autoReplaceSvg = false;
@@ -124,6 +127,7 @@ library.add(faCircleExclamation);
library.add(faCircleInfo);
library.add(faPaintRoller);
library.add(faTicket);
+library.add(faLinkSlash);
// Front
library.add(faChevronLeft);
@@ -137,4 +141,6 @@ library.add(faCopy);
library.add(faClipboard);
library.add(faXmark);
library.add(faArrowRightFromBracket);
+library.add(faTriangleExclamation);
+library.add(faEquals);
dom.watch();
diff --git a/resources/views/bo/modules/change-model-order.blade.php b/resources/views/bo/modules/change-model-order.blade.php
new file mode 100644
index 0000000..a304ff0
--- /dev/null
+++ b/resources/views/bo/modules/change-model-order.blade.php
@@ -0,0 +1,42 @@
+