Compare commits
203 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5634d4d232 | ||
|
|
9a1a07307b | ||
|
|
8410447a29 | ||
|
|
f0fb7a26f3 | ||
|
|
f55ca959b8 | ||
|
|
d622f543e9 | ||
|
|
1f1c684008 | ||
|
|
e8c5472d45 | ||
|
|
4ff19d9277 | ||
|
|
7dfd80cf23 | ||
|
|
e19d810a2f | ||
|
|
ab998c975d | ||
|
|
9706922312 | ||
|
|
ebb52b5968 | ||
|
|
6c783247a7 | ||
|
|
6cf790feab | ||
|
|
57c7613455 | ||
|
|
eddeb4f7f6 | ||
|
|
d9d1fea624 | ||
|
|
22d79cfb11 | ||
|
|
22bc49baab | ||
|
|
adb2a8b21c | ||
|
|
7246d7894d | ||
|
|
10b75031e7 | ||
|
|
68a48fdf5a | ||
|
|
a7debacd65 | ||
|
|
dd10d4ba3a | ||
|
|
a7bd6d6420 | ||
|
|
8cfc409d5c | ||
|
|
bc7a1020bc | ||
|
|
e828c8bf66 | ||
|
|
f28dbdd1df | ||
|
|
965226fae6 | ||
|
|
bd9b7cd32e | ||
|
|
8c67989526 | ||
|
|
2212b0ae08 | ||
|
|
c2b25c3503 | ||
|
|
f106c17588 | ||
|
|
8e9d117918 | ||
|
|
0dbb483fc4 | ||
|
|
980717f37c | ||
|
|
020fcdaa03 | ||
|
|
095f80eac1 | ||
|
|
6a973703c4 | ||
|
|
9a853088b1 | ||
|
|
0ec3be521d | ||
|
|
6854af708b | ||
|
|
8f1c920e26 | ||
|
|
efe5e8ace6 | ||
|
|
91f4773c60 | ||
|
|
6b5b4ec619 | ||
|
|
4edd92e572 | ||
|
|
da03fbe273 | ||
|
|
b7f7b7c55d | ||
|
|
49d37a8fa6 | ||
|
|
195a5fcb3e | ||
|
|
e0af4a243a | ||
|
|
0c4d376c22 | ||
|
|
6fabb998ed | ||
|
|
04bd156849 | ||
|
|
a43037d2f9 | ||
|
|
ef57fe9fe5 | ||
|
|
62901a015e | ||
|
|
cbabb40f83 | ||
|
|
870986faa4 | ||
|
|
33ea75a5ed | ||
|
|
3e09baf6f3 | ||
|
|
d5f1891764 | ||
|
|
098e40576e | ||
|
|
18b66793a0 | ||
|
|
7ee8a2be82 | ||
|
|
87cc2dec86 | ||
|
|
ea41e19724 | ||
|
|
65772aeb1d | ||
|
|
ea6ac998d8 | ||
|
|
9bc3d9e5e0 | ||
|
|
f99dac8aab | ||
|
|
3f4342f3bb | ||
|
|
481c732f3e | ||
|
|
887837dcaf | ||
|
|
4bdcf60dfa | ||
|
|
435d5626ee | ||
|
|
50ba8eaf8a | ||
|
|
f2a65c85b0 | ||
|
|
1a2fe21212 | ||
|
|
d702c02bdb | ||
|
|
b1a6dfb72c | ||
|
|
844928e945 | ||
|
|
3560e6c7f6 | ||
|
|
aa3f507483 | ||
|
|
8a34a30ab4 | ||
|
|
4a0c556937 | ||
|
|
085028a465 | ||
|
|
f9ed83ff60 | ||
|
|
1ffb5e5bdd | ||
|
|
c6fcd7cfa6 | ||
|
|
3d81cd77ce | ||
|
|
fba178b598 | ||
|
|
1f02f86610 | ||
|
|
efb3914e4f | ||
|
|
b0f4c37153 | ||
|
|
25ab578c93 | ||
|
|
52386bbfd4 | ||
|
|
d6ed7c1f04 | ||
|
|
5477affb97 | ||
|
|
3e199d0f68 | ||
|
|
7564600ace | ||
|
|
ba6f7f8e7b | ||
|
|
f1eddb1cc2 | ||
|
|
658fedd3eb | ||
|
|
31510149af | ||
|
|
fb8720d41b | ||
|
|
08afe201f8 | ||
|
|
afd8f09d02 | ||
|
|
33cf4f1c93 | ||
|
|
e1944924c1 | ||
|
|
3a0cc47fa8 | ||
|
|
3a2b5a0af9 | ||
|
|
ca7057ce77 | ||
|
|
0dd283b68d | ||
|
|
7c028a4505 | ||
|
|
b21dd4d260 | ||
|
|
4838b54b4c | ||
|
|
911ee5df4b | ||
|
|
bbfd4360f2 | ||
|
|
3a51ccb0f3 | ||
|
|
1c3f20782b | ||
|
|
673494aeda | ||
|
|
1bd04e2e52 | ||
|
|
aa17aab45f | ||
|
|
ec47d2610f | ||
|
|
560ef30cd8 | ||
|
|
b2908aac9f | ||
|
|
0da4a6fa13 | ||
|
|
b6720bae3d | ||
|
|
a58bf2ff3b | ||
|
|
2b94d780b6 | ||
|
|
74edc5c785 | ||
|
|
781161c5e4 | ||
|
|
2d70ff9f29 | ||
|
|
17a01e1f7a | ||
|
|
e3d828a9e5 | ||
|
|
d17678342e | ||
|
|
b646cb41fe | ||
|
|
5bd7673a18 | ||
|
|
2faf043f68 | ||
|
|
66c889b02a | ||
|
|
74600ef3a7 | ||
|
|
35e18112b9 | ||
|
|
f10b46a012 | ||
|
|
523715a3c1 | ||
|
|
c8b4f97c88 | ||
|
|
65b92edc99 | ||
|
|
2ee245be4c | ||
|
|
766a990361 | ||
|
|
a23509fa09 | ||
|
|
6c8cc25bae | ||
|
|
3c529a7bc0 | ||
|
|
c8d179e7b3 | ||
|
|
8e4d6d4198 | ||
|
|
a3c2697c86 | ||
|
|
c6d89101b4 | ||
|
|
ad43378b52 | ||
|
|
f40f2e2aa5 | ||
|
|
b55d812aaa | ||
|
|
ab08bad87e | ||
|
|
e8b920a8cd | ||
|
|
cb779c83b8 | ||
|
|
a393b24392 | ||
|
|
cd89f6b443 | ||
|
|
d9b316eae4 | ||
|
|
722369cf61 | ||
|
|
0cc315d2ef | ||
|
|
7559a47044 | ||
|
|
55fef3421e | ||
|
|
97d8111f4b | ||
|
|
4354f6a445 | ||
|
|
abdbbae843 | ||
|
|
f150e96d39 | ||
|
|
d2324145a4 | ||
|
|
b6c74efd3d | ||
|
|
57a437db48 | ||
|
|
f3cb8e7974 | ||
|
|
91c1fe5532 | ||
|
|
29a837ff61 | ||
|
|
bbe4478c09 | ||
|
|
28ee9f593d | ||
|
|
eb2d4e5267 | ||
|
|
c19d5829e6 | ||
|
|
c1fb743e34 | ||
|
|
1d07c93799 | ||
|
|
32cb11622c | ||
|
|
911d7a2f6d | ||
|
|
32be1746b5 | ||
|
|
a5d4c58604 | ||
|
|
0d6f34b05a | ||
|
|
606ca09608 | ||
|
|
6523875860 | ||
|
|
83b7e42b90 | ||
|
|
6f20a6b8db | ||
|
|
88f3da4c92 | ||
|
|
bd3133f06c | ||
|
|
bb2cbc5ad8 |
75 changed files with 7913 additions and 1795 deletions
|
|
@ -7,7 +7,7 @@ include:
|
|||
file: 'security/security.yml'
|
||||
- project: 'ldc-web/shared-ci'
|
||||
ref: 'main'
|
||||
file: 'build/build.yml'
|
||||
file: "build/docker-multistage.yml"
|
||||
- project: 'ldc-web/shared-ci'
|
||||
ref: 'main'
|
||||
file: 'test/test.yml'
|
||||
|
|
|
|||
44
Dockerfile
44
Dockerfile
|
|
@ -1,5 +1,43 @@
|
|||
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.3:170625-101047
|
||||
COPY --chown=www-data:www-data . /app/srcs
|
||||
FROM composer:2.9.2 AS vendor
|
||||
|
||||
ARG GITLAB_USER
|
||||
ARG GITLAB_PASSWORD
|
||||
|
||||
COPY database/ database/
|
||||
|
||||
COPY composer.json composer.json
|
||||
COPY composer.lock composer.lock
|
||||
|
||||
RUN composer config gitlab-token.lab.s-web.fr ${GITLAB_USER} ${GITLAB_PASSWORD}
|
||||
RUN composer install \
|
||||
--ignore-platform-reqs \
|
||||
--no-interaction \
|
||||
--no-plugins \
|
||||
--no-scripts \
|
||||
--no-dev \
|
||||
--prefer-dist
|
||||
|
||||
FROM node:20.18 AS frontend
|
||||
|
||||
ARG GITLAB_PASSWORD
|
||||
|
||||
RUN mkdir -p /app/public/build
|
||||
|
||||
COPY package*.json tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/
|
||||
COPY resources/ /app/resources/
|
||||
WORKDIR /app
|
||||
RUN npm config set @uwil:registry https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/
|
||||
RUN npm config set -- '//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
|
||||
RUN npm ci --no-audit --no-fund --loglevel=error && npm run prod
|
||||
|
||||
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-production-8.5:${PHP_8_5_TAG}
|
||||
|
||||
ARG USER=www-data
|
||||
|
||||
COPY --chown=${USER}:${USER} . /app/srcs
|
||||
COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
|
||||
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
|
||||
|
||||
USER root
|
||||
RUN chmod -R 755 /app/srcs/storage
|
||||
USER www-data
|
||||
USER ${USER}
|
||||
|
|
|
|||
4633
_ide_helper.php
Normal file
4633
_ide_helper.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,16 +2,17 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Models\Ticket;
|
||||
use App\Enums\Tickets\TicketStatusEnum;
|
||||
use App\Models\GitlabIssues;
|
||||
use App\Models\GitlabLabels;
|
||||
use App\Models\GitlabProjects;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\GitlabMilestones;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Enums\Tickets\TicketStatusEnum;
|
||||
use App\Models\GitlabProjects;
|
||||
use App\Models\Ticket;
|
||||
use Carbon\Carbon;
|
||||
use GrahamCampbell\GitLab\Facades\GitLab;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UpdateGitlabDatas extends Command
|
||||
{
|
||||
|
|
@ -226,10 +227,36 @@ class UpdateGitlabDatas extends Command
|
|||
$page = 1;
|
||||
|
||||
do {
|
||||
$milestones = GitLab::milestones()->all($projectId, [
|
||||
'per_page' => 100,
|
||||
'page' => $page
|
||||
]);
|
||||
$milestones = [];
|
||||
|
||||
try {
|
||||
$milestones = GitLab::milestones()->all($projectId, [
|
||||
'per_page' => 100,
|
||||
'page' => $page
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
if ($project->delete()) {
|
||||
Log::warning(sprintf(
|
||||
"Project not found (%s), it has been deleted.",
|
||||
$projectId
|
||||
));
|
||||
} else {
|
||||
Log::warning(sprintf(
|
||||
"Project not found (%s).",
|
||||
$projectId
|
||||
));
|
||||
}
|
||||
} else {
|
||||
Log::error(sprintf(
|
||||
'API Error for project %s, Message: %d',
|
||||
$projectId,
|
||||
$e->getMessage()
|
||||
));
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
$allMilestones = array_merge($allMilestones, $milestones);
|
||||
$page++;
|
||||
} while (!empty($milestones));
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
$schedule->job(ExportTicketsState::class)->weekly()->mondays()->at('07:00');
|
||||
$schedule->job(ExportTicketsState::class)->weekly()->fridays()->at('14:00');
|
||||
$schedule->job(AutoCloseOldTickets::class)->dailyAt('08:00');
|
||||
$schedule->job(WarnBeforeAutoCloseTicket::class)->dailyAt('08:05');
|
||||
$schedule->command('app:update-gitlab-datas --is-cron')->hourly()->between('7:00', '19:00');
|
||||
|
|
|
|||
13
app/Dto/BrevoWebHookDto.php
Normal file
13
app/Dto/BrevoWebHookDto.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
final readonly class BrevoWebHookDto
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $plainTextContent,
|
||||
public readonly ?string $brand
|
||||
) {}
|
||||
}
|
||||
30
app/Events/BrevoWebhookCallReceived.php
Normal file
30
app/Events/BrevoWebhookCallReceived.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Domain\Models\ValueObjects\Email;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
readonly class BrevoWebhookCallReceived
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
public function __construct(
|
||||
public Email $from,
|
||||
public Email $to,
|
||||
public string $htmlRawContent
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('default'),
|
||||
];
|
||||
}
|
||||
}
|
||||
34
app/Events/TicketCreated.php
Normal file
34
app/Events/TicketCreated.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TicketCreated implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public readonly Site $siteReceivingTicket,
|
||||
public readonly Ticket $ticket,
|
||||
public readonly string $byUserName
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('default'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ticket;
|
||||
use App\Mail\NewTicket;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Enums\Tickets\TicketTypeEnum;
|
||||
use App\Enums\Tickets\TicketStatusEnum;
|
||||
use App\Http\Requests\Bo\BrevoWehbookRequest;
|
||||
|
||||
class BrevoController extends Controller
|
||||
{
|
||||
public function createTicket(BrevoWehbookRequest $request)
|
||||
{
|
||||
$data = [];
|
||||
$site = Site::where('name', 'Mailing en masse')->firstOrFail();
|
||||
|
||||
$formContent = $request->items[0]["ExtractedMarkdownMessage"];
|
||||
|
||||
$tblRegexPatterns = [
|
||||
'user' => '/Matricule et Nom du demandeur\s*\|\s*([^|]+?)\s*\|/i',
|
||||
'email' => '/Email du demandeur\s*\|\s*([^|]+?)\s*\|/i',
|
||||
'brand' => '/Marque\s*\/\s*Filiale\s*\|\s*([^|]+?)\s*\|/i',
|
||||
'subject' => '/Pour quels besoins avez-vous besoin d\'envoyer des emails en masse\s*\?\s*\|\s*([^|]+?)\s*\|/is',
|
||||
'date_mep' => '/Date de première envoi souhaité\s*\|\s*([^|]+?)\s*\|/i',
|
||||
'comment' => '/Commentaire\s*\|\s*([^|]+?)(?=\s*\|)/i',
|
||||
];
|
||||
|
||||
foreach ($tblRegexPatterns as $field => $regex) {
|
||||
if (preg_match($regex, $formContent, $matches)) {
|
||||
$data[$field] = trim($matches[1]);
|
||||
} else {
|
||||
return response()->json(['reason' => "Champ manquant $field"], 422);
|
||||
}
|
||||
}
|
||||
|
||||
if ($data['date_mep']) {
|
||||
$dateMep = Carbon::createFromFormat('d/m/Y', $data['date_mep'])->format('Y-m-d');
|
||||
}
|
||||
|
||||
$ticket = new Ticket([
|
||||
'site_id' => $site->id,
|
||||
'type' => TicketTypeEnum::mailing->value,
|
||||
'status' => TicketStatusEnum::new->value,
|
||||
'date_mep' => $dateMep ?? null,
|
||||
'text' => "
|
||||
Bonjour,\n
|
||||
Nom du demandeur : {$data['user']},
|
||||
Adresse mail : {$data['email']},
|
||||
Marque / Filiale concernée : {$data['brand']},
|
||||
Sujet : {$data['subject']},
|
||||
Date d'envoi souhaitée : {$data['date_mep']},
|
||||
Commentaire : {$data['comment']},\n
|
||||
Cordialement.
|
||||
",
|
||||
'title' => "Demande de mail en masse pour {$data['brand']}",
|
||||
'is_internal' => false
|
||||
]);
|
||||
|
||||
$ticket->user()->associate($site->projectManager);
|
||||
$ticket->site()->associate($site);
|
||||
$ticket->assignedUser()->associate($site->projectManager);
|
||||
$ticket->saveOrFail();
|
||||
|
||||
if (config('mail.MAIL_ADMIN')) {
|
||||
Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket));
|
||||
} else {
|
||||
throw new \ErrorException('Missing MAIL_ADMIN in .env');
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
58
app/Http/Controllers/Bo/BrevoWebhookController.php
Normal file
58
app/Http/Controllers/Bo/BrevoWebhookController.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Events\BrevoWebhookCallReceived;
|
||||
use App\Http\Requests\Bo\BrevoWebhookRequest;
|
||||
use Domain\Models\ValueObjects\Email;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use \Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class BrevoWebhookController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly Dispatcher $dispatcher
|
||||
) {}
|
||||
|
||||
public function webhookCallReceived(BrevoWebhookRequest $request): JsonResponse
|
||||
{
|
||||
$fromEmail = $request->getFromAddress();
|
||||
$toEmail = $request->getToAddress();
|
||||
$formContent = $request->getRawHtmlBody();
|
||||
|
||||
if (!$this->isFromLdc($fromEmail)) {
|
||||
$this->logger->warning(
|
||||
'Received a brevo webhook call from a non ldc email.',
|
||||
[
|
||||
'from' => $fromEmail,
|
||||
'to' => $toEmail,
|
||||
'content' => base64_encode($formContent)
|
||||
]
|
||||
);
|
||||
|
||||
return response()->json(
|
||||
['ok' => false],
|
||||
Response::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch(
|
||||
new BrevoWebhookCallReceived(
|
||||
from: $fromEmail,
|
||||
to: $toEmail,
|
||||
htmlRawContent: $formContent,
|
||||
)
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
private function isFromLdc(Email $email): bool
|
||||
{
|
||||
return substr(strrchr($email->value, "@"), 1) === 'ldc.fr';
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ use App\Http\Requests\Bo\StoreTicket;
|
|||
use App\Http\Requests\Bo\UpdateTicket;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Enums\Tickets\TicketStatusEnum;
|
||||
use App\Events\TicketCreated;
|
||||
use App\Http\Requests\Bo\CreateMessage;
|
||||
use App\Http\Requests\Bo\UpdateMessage;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
|
@ -33,14 +34,16 @@ use App\Http\Requests\Bo\ChangeTicketStatus;
|
|||
use App\Http\Requests\Bo\UpdateChangeOrderRequest;
|
||||
use App\Http\Requests\Bo\AssignTicketToUserRequest;
|
||||
use App\Http\Requests\Bo\UpdateTicketPriorityRequest;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class TicketController extends Controller
|
||||
{
|
||||
/**
|
||||
* TicketController
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Dispatcher $dispatcher
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->authorizeResource(Ticket::class, 'ticket');
|
||||
}
|
||||
|
|
@ -213,7 +216,7 @@ class TicketController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$ticket->user()->associate(auth()->user());
|
||||
$ticket->user()->associate($user);
|
||||
$ticket->site()->associate($site);
|
||||
$ticket->saveOrFail();
|
||||
|
||||
|
|
@ -223,11 +226,11 @@ class TicketController extends Controller
|
|||
|
||||
$user->subscriptions()->toggle($ticket->id);
|
||||
|
||||
if (config('mail.MAIL_ADMIN')) {
|
||||
Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket));
|
||||
} else {
|
||||
throw new \ErrorException('Missing MAIL_ADMIN in .env');
|
||||
}
|
||||
$this->dispatcher->dispatch(new TicketCreated(
|
||||
siteReceivingTicket: $site,
|
||||
ticket: $ticket,
|
||||
byUserName: $user->name
|
||||
));
|
||||
|
||||
return redirect()->route('bo.ticket.show', $ticket)
|
||||
->with('success', trans('Ticket enregistré !'));
|
||||
|
|
@ -751,16 +754,23 @@ class TicketController extends Controller
|
|||
{
|
||||
$userGitlabId = auth()->user()->gitlab_id;
|
||||
$gitlabRepoId = $ticket->site->gitlab_project_id;
|
||||
$description = \sprintf("%s<br>", $ticket->text);
|
||||
$cLabel = match ($ticket->type) {
|
||||
TicketTypeEnum::bug => 'c-bug,to-dispatch',
|
||||
TicketTypeEnum::evolution => 'c-feature',
|
||||
|
||||
if (GitLab::projects()->show($gitlabRepoId)['archived']) {
|
||||
return back()->with('error', \trans("Ce projet GitLab est archivé et ne peut pas être utilisé."));
|
||||
}
|
||||
|
||||
$description = \sprintf("%s<br>", $ticket->text);
|
||||
$cLabel = match ($ticket->type) {
|
||||
TicketTypeEnum::bug => 'c-bug,g-to-dispatch',
|
||||
TicketTypeEnum::evolution,
|
||||
TicketTypeEnum::mailing => 'c-feature',
|
||||
TicketTypeEnum::newsletter => 'c-feature,g-to-estimate',
|
||||
};
|
||||
|
||||
$assigneeId = match ($ticket->type) {
|
||||
TicketTypeEnum::bug => $userGitlabId,
|
||||
TicketTypeEnum::evolution,
|
||||
TicketTypeEnum::mailing,
|
||||
TicketTypeEnum::newsletter => $ticket->site->projectManager?->gitlab_id
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -73,8 +73,9 @@ class UserController extends Controller
|
|||
public function store(StoreUserRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$user = new User();
|
||||
$user = User::query()->onlyTrashed()->where('email', $request->validated('email'))->first() ?? new User();
|
||||
$user->fill($request->safe()->except('sites'));
|
||||
$user->deleted_at = null;
|
||||
|
||||
if ($user->saveOrFail()) {
|
||||
$user->sites()->attach(collect($request->validated('sites'))->pluck('id'));
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class RedirectIfAuthenticated
|
|||
switch ($guard) {
|
||||
case 'backend':
|
||||
if (!$request->routeIs('bo.*') && !$request->routeIs('login')) {
|
||||
continue;
|
||||
continue 2;
|
||||
}
|
||||
return \redirect()->route('bo.dashboard');
|
||||
default:
|
||||
|
|
|
|||
50
app/Http/Requests/Bo/BrevoWebhookRequest.php
Normal file
50
app/Http/Requests/Bo/BrevoWebhookRequest.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo;
|
||||
|
||||
use Domain\Models\ValueObjects\Email;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
class BrevoWebhookRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFromAddress(): Email
|
||||
{
|
||||
return new Email($this->string('items.0.From.Address'));
|
||||
}
|
||||
|
||||
public function getToAddress(): Email
|
||||
{
|
||||
return new Email((string) $this->input('items.0.To.0.Address'));
|
||||
}
|
||||
|
||||
public function getRawHtmlBody(): string
|
||||
{
|
||||
return $this->string('items.0.RawHtmlBody');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$expectedFormAddress = Config::string('mail.brevo_webook.expected_from_address');
|
||||
|
||||
return [
|
||||
'items' => ['required', 'array', 'min:1'],
|
||||
'items.0.From.Address' => ['required', 'email:rfc,spoof'],
|
||||
'items.0.To.*.Address' => ['required', 'email:rfc,spoof', "in:{$expectedFormAddress}"],
|
||||
'items.0.RawHtmlBody' => ['required', 'string', 'min:10'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BrevoWehbookRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'items' => 'required|array|min:1',
|
||||
'items.0.From.Address' => 'required|email',
|
||||
'items.0.To.*.Address' => 'required|email|in:mailing@reply.uwil.fr',
|
||||
'items.0.ExtractedMarkdownMessage' => 'required|string|min:10',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,11 @@ class StoreUserRequest extends FormRequest
|
|||
{
|
||||
return [
|
||||
'name' => 'required|string|min:3|max:255',
|
||||
'email' => 'email|required|unique:users,email',
|
||||
'email' => [
|
||||
'email',
|
||||
'required',
|
||||
Rule::unique('users', 'email')->whereNull('deleted_at')
|
||||
],
|
||||
'role' => [
|
||||
'required',
|
||||
Rule::in(collect(RoleEnum::cases())->pluck('value')->all()),
|
||||
|
|
|
|||
75
app/Listeners/CreateTicketAutomatically.php
Normal file
75
app/Listeners/CreateTicketAutomatically.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\BrevoWebhookCallReceived;
|
||||
use App\Events\TicketCreated;
|
||||
use App\Mail\NewTicket;
|
||||
use App\Repositories\SiteRepository;
|
||||
use App\Services\AutomaticTicketCreator;
|
||||
use Domain\Models\ValueObjects\Email;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
// NOTE: this should be queued in order to be eventually retried or at least keep info data.
|
||||
class CreateTicketAutomatically implements ShouldQueue
|
||||
{
|
||||
private const string SITE_NAME_WHERE_TO_CREATE_TICKET = 'Mailing en masse';
|
||||
|
||||
public function __construct(
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly AutomaticTicketCreator $automaticTicketCreator,
|
||||
private readonly SiteRepository $siteRepository,
|
||||
private readonly Dispatcher $dispatcher
|
||||
) {}
|
||||
|
||||
public function handle(BrevoWebhookCallReceived $event): void
|
||||
{
|
||||
$fromEmail = $event->from;
|
||||
$toEmail = $event->to;
|
||||
$htmlRawContent = $event->htmlRawContent;
|
||||
|
||||
$this->logger->info('Received a ticket creation event from brevo');
|
||||
$this->logger->debug(
|
||||
'Payload for ticket creation form brevo',
|
||||
[
|
||||
'from_email' => $fromEmail->value,
|
||||
'to_email' => $toEmail->value,
|
||||
'html_raw_content' => base64_encode($htmlRawContent)
|
||||
]
|
||||
);
|
||||
|
||||
$siteReceivingTickets = $this->siteRepository->findByName(self::SITE_NAME_WHERE_TO_CREATE_TICKET);
|
||||
|
||||
if ($siteReceivingTickets === null) {
|
||||
throw new \UnexpectedValueException(
|
||||
'No site found with name \'' .
|
||||
self::SITE_NAME_WHERE_TO_CREATE_TICKET . '\''
|
||||
);
|
||||
}
|
||||
|
||||
$projectManager = $siteReceivingTickets->projectManager()->first();
|
||||
|
||||
if ($projectManager === null) {
|
||||
throw new \UnexpectedValueException(
|
||||
'No project manager found for site \'' .
|
||||
self::SITE_NAME_WHERE_TO_CREATE_TICKET . '\''
|
||||
);
|
||||
}
|
||||
|
||||
$ticket = $this->automaticTicketCreator->createTicket(
|
||||
$siteReceivingTickets,
|
||||
$projectManager,
|
||||
$htmlRawContent,
|
||||
);
|
||||
|
||||
$this->dispatcher->dispatch(new TicketCreated(
|
||||
siteReceivingTicket: $siteReceivingTickets,
|
||||
ticket: $ticket,
|
||||
byUserName: $fromEmail->value
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,21 +3,51 @@
|
|||
namespace App\Listeners;
|
||||
|
||||
use App\Events\NotifyAdmin;
|
||||
use App\Events\TicketCreated;
|
||||
use App\Mail\NewTicket;
|
||||
use App\Mail\TicketUpdate;
|
||||
use Domain\Models\ValueObjects\Email;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendNotificationMailToAdmin
|
||||
{
|
||||
private readonly Email $mailAdmin;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->mailAdmin = new Email(
|
||||
Config::string('mail.MAIL_ADMIN')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param \App\Events\NotifyAdmin $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(NotifyAdmin $event)
|
||||
public function handle(NotifyAdmin | TicketCreated $event)
|
||||
{
|
||||
$ticket = $event->ticket;
|
||||
Mail::to(config('mail.MAIL_ADMIN'))
|
||||
->send(new TicketUpdate($ticket, $event->corpsMaj));
|
||||
if ($event instanceof NotifyAdmin) {
|
||||
$ticket = $event->ticket;
|
||||
Mail::to($this->mailAdmin->value)
|
||||
->send(new TicketUpdate($ticket, $event->corpsMaj));
|
||||
}
|
||||
if ($event instanceof TicketCreated) {
|
||||
$ticket = $event->ticket;
|
||||
$siteReceivingTickets = $event->siteReceivingTicket;
|
||||
$userName = $event->byUserName;
|
||||
|
||||
Mail::to($this->mailAdmin->value)
|
||||
->send(new NewTicket(
|
||||
ticketId: $ticket->id,
|
||||
siteName: $siteReceivingTickets->name,
|
||||
ticketTitle: $ticket->title,
|
||||
ticketType: $ticket->type,
|
||||
userName: $userName,
|
||||
ticketDescription: $ticket->text,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,29 +2,23 @@
|
|||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Ticket;
|
||||
use App\Enums\Tickets\TicketTypeEnum;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class NewTicket extends Mailable
|
||||
class NewTicket extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/** @var \App\Models\Ticket */
|
||||
private $ticket;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param \App\Models\Ticket $ticket
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Ticket $ticket)
|
||||
{
|
||||
$this->ticket = $ticket;
|
||||
}
|
||||
public function __construct(
|
||||
private readonly int $ticketId,
|
||||
private readonly string $siteName,
|
||||
private readonly string $ticketTitle,
|
||||
private readonly TicketTypeEnum $ticketType,
|
||||
private readonly string $userName,
|
||||
private readonly string $ticketDescription,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
|
|
@ -33,8 +27,14 @@ class NewTicket extends Mailable
|
|||
*/
|
||||
public function build()
|
||||
{
|
||||
$ticket = $this->ticket;
|
||||
return $this->subject("[Support] Nouveau ticket n° $ticket->id - $ticket->title")
|
||||
->view('emails.new-ticket', compact('ticket'));
|
||||
return $this->subject("[Support] Nouveau ticket n° {$this->ticketId} - {$this->ticketTitle}")
|
||||
->view('emails.new-ticket', [
|
||||
'ticketId' => $this->ticketId,
|
||||
'siteName' => $this->siteName,
|
||||
'ticketTitle' => $this->ticketTitle,
|
||||
'ticketType' => $this->ticketType,
|
||||
'userName' => $this->userName,
|
||||
'ticketDescription' => $this->ticketDescription,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property string $name
|
||||
* @property int $id Doit correspondre aux ids marques de la CRM
|
||||
* @property string $name Nom de la marque
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Account> $accounts
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Site> $sites
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Brand extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,14 @@ use Illuminate\Database\Eloquent\Model;
|
|||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
|
||||
* @property-read mixed $value
|
||||
* @method static \Database\Factories\CompanyFactory factory($count = null, $state = [])
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Company extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,10 +7,16 @@ use App\Models\Brand;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property string $label
|
||||
* @property AccountTypeEnum $type
|
||||
* @property integer $balance
|
||||
* @property Brand $brand_id
|
||||
* @property int $id
|
||||
* @property AccountTypeEnum $type Enum Credit\AccountTypeEnum
|
||||
* @property string $label Custom account label
|
||||
* @property float $balance Account balance
|
||||
* @property int $brand_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read Brand $brand
|
||||
* @property-read \App\Models\Credit\CreditLog|null $creditlogs
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Account extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,10 +6,15 @@ use App\Enums\Credit\BalanceOperationEnum;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property BalanceOperationEnum $type
|
||||
* @property double $amount
|
||||
* @property Account $credit_account_id
|
||||
* @property date $created_at
|
||||
* @property int $id
|
||||
* @property BalanceOperationEnum $type Enum Credit\BalanceOperationEnum
|
||||
* @property float $amount Montant de l'opération
|
||||
* @property int $credit_account_id
|
||||
* @property string $created_at
|
||||
* @property-read \App\Models\Credit\Account $account
|
||||
* @property-read \App\Models\Credit\EstimationLog|null $pivot
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit\Estimation> $estimation
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class CreditLog extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,22 @@ use App\Models\User;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property EstimationStatusEnum $status Enum Credit\EstimationStatusEnum
|
||||
* @property numeric $amount Estimation amount
|
||||
* @property int $ticket_id
|
||||
* @property int $credit_account_id The account to debit.
|
||||
* @property int $estimator_id The person who has done the estimate.
|
||||
* @property int|null $validator_id The person who has validate the estimate.
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Credit\Account $account
|
||||
* @property-read User|null $estimator
|
||||
* @property-read Ticket|null $ticket
|
||||
* @property-read User|null $validator
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Estimation extends Model
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@ namespace App\Models\Credit;
|
|||
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $credit_log_id
|
||||
* @property int $credit_estimation_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class EstimationLog extends Pivot
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,6 +4,26 @@ namespace App\Models;
|
|||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $title Gitlab issue title
|
||||
* @property string $url Gitlab URL
|
||||
* @property int $estimate_time Gitlab issue estimate time in seconds
|
||||
* @property int $spent_time Gitlab issue spent time in seconds
|
||||
* @property int $is_closed Gitlab issue status
|
||||
* @property int $gitlab_id Gitlab issue id
|
||||
* @property int $gitlab_iid Gitlab issue iid in project
|
||||
* @property int|null $milestone_iid Gitlab issue linked milestone iid in project
|
||||
* @property int $gitlab_project_id Gitlab project id
|
||||
* @property string $g_created_at Gitlab issue created at
|
||||
* @property string $g_updated_at Gitlab issue updated at
|
||||
* @property string|null $g_closed_at Gitlab issue closed at
|
||||
* @property string|null $g_due_date Gitlab issue due date
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\GitlabLabels> $gitlabLabels
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class GitlabIssues extends Model
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,6 +4,15 @@ namespace App\Models;
|
|||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name Label name
|
||||
* @property int $gitlab_id Label Gitlab id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\GitlabIssues> $issues
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class GitlabLabels extends Model
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,6 +4,22 @@ namespace App\Models;
|
|||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $title Milestone title
|
||||
* @property string $url Gitlab URL
|
||||
* @property int $is_closed Gitlab milestone status
|
||||
* @property int $gitlab_id Gitlab milestone id
|
||||
* @property int $gitlab_iid Gitlab milestone iid in project
|
||||
* @property int $gitlab_project_id Gitlab project id
|
||||
* @property string $g_created_at Gitlab milestone created at
|
||||
* @property string $g_updated_at Gitlab milestone updated at
|
||||
* @property string|null $g_start_date Gitlab milestone start date
|
||||
* @property string|null $g_due_date Gitlab milestone due date
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class GitlabMilestones extends Model
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@ namespace App\Models;
|
|||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name Gitlab project name
|
||||
* @property string $url Gitlab URL
|
||||
* @property int $is_archived Gitlab archive status
|
||||
* @property int $gitlab_id Gitlab unique ID
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class GitlabProjects extends Model
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,9 +7,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $title
|
||||
* @property string $message
|
||||
* @property InfoColorsEnum $type
|
||||
* @property InfoColorsEnum $type Defined by the InfoColorsEnum enum.
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Information extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,22 +13,34 @@ use Mccarlosen\LaravelMpdf\LaravelMpdf;
|
|||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @property string $ref
|
||||
* @property string $firstname
|
||||
* @property string $lastname
|
||||
* @property string $object
|
||||
* @property string $tasks
|
||||
* @property double $total
|
||||
* @property double $total_days
|
||||
* @property string $comment
|
||||
* @property boolean $show_days
|
||||
* @property date $signed_date
|
||||
* @property date $sent_client_date
|
||||
* @property date $sent_accounting_date
|
||||
* @property Site $site_id
|
||||
* @property Company $company_id
|
||||
* @property Company $invoiced_to_id
|
||||
* @property InvoiceStatusEnum $status
|
||||
* @property int $id
|
||||
* @property string $ref The invoice reference built as YYYY_MM_INCREMENT_USER. INCREMENT is the N invoice of the month. USER is the first two letters of the user who last edited the invoice. This field has to generated only on create.
|
||||
* @property string $firstname The person to which the invoice is addressed.
|
||||
* @property string $lastname The person to which the invoice is addressed.
|
||||
* @property string $object The invoice object.
|
||||
* @property array<array-key, mixed> $tasks A JSON object, each line has to be contructed using these fields description, days, type Invoices\TaskTypeEnum, amount
|
||||
* @property float $total The total calculated using all tasks
|
||||
* @property float $total_days The total days calculated using all tasks
|
||||
* @property string|null $comment Any comment joined to the invoice
|
||||
* @property int $show_days Show days on the printed invoice
|
||||
* @property Carbon|null $signed_date The date the invoice was signed
|
||||
* @property Carbon|null $sent_client_date The date the invoice was send to the client
|
||||
* @property Carbon|null $sent_accounting_date The date the invoice was send to the client
|
||||
* @property int $site_id
|
||||
* @property int $company_id
|
||||
* @property int $invoiced_to_id
|
||||
* @property InvoiceStatusEnum $status Status of the invoice.
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property-read \App\Models\Company $company
|
||||
* @property-read \App\Models\Company $companyInvoiced
|
||||
* @property-read \App\Models\Site|null $site
|
||||
* @method static \Database\Factories\InvoiceFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Invoice onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Invoice withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Invoice withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Invoice extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@ use App\Lib\Helpers\ToolboxHelper;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name Un ou deux mot désignant le label
|
||||
* @property string $color Cast HTML color
|
||||
* @property-read \App\Models\Site|null $site
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Ticket> $tickets
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Label extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
|
|
|||
|
|
@ -9,12 +9,20 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
*
|
||||
* @property string $message The message content.
|
||||
* @property \App\Enums\MessageTypeEnum $type The message is not visible for clients.
|
||||
* @property string $file The message joined file path.
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int $ticket_id
|
||||
* @property int|null $user_id
|
||||
* @property string $message
|
||||
* @property MessageTypeEnum $type Message Type defined by an enum.
|
||||
* @property string|null $file
|
||||
* @property string|null $file2 Pièce jointes n°2
|
||||
* @property string|null $file3 Pièce jointes n°3
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Ticket|null $ticket
|
||||
* @property-read \App\Models\User|null $user
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Message extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,8 +6,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property double $amount
|
||||
* @property float $amount The cost per day
|
||||
* @property bool $published
|
||||
* @method static \Database\Factories\PackageFactory factory($count = null, $state = [])
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Package extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,11 +10,26 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @property string $name
|
||||
* @property string $url_re7
|
||||
* @property string $url_prod
|
||||
* @property integer $gitlab_project_id
|
||||
* @property Brand $brand_id
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string|null $url_re7
|
||||
* @property string|null $url_prod
|
||||
* @property int|null $gitlab_project_id
|
||||
* @property int|null $brand_id Relation is nullable, since not all site would be affiliate with a brand.
|
||||
* @property int|null $project_manager_id Project manager relationship
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property-read \App\Models\Brand|null $brand
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
|
||||
* @property-read \App\Models\User|null $projectManager
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Ticket> $tickets
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
|
||||
* @property-read mixed $value
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Site onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Site withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Site withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Site extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ namespace App\Models;
|
|||
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
/**
|
||||
* @property int $site_id
|
||||
* @property int $user_id
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class SiteUser extends Pivot
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -22,6 +22,44 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $created_by
|
||||
* @property int|null $assigned_to La personne a qui est assigné le ticket.
|
||||
* @property int $site_id
|
||||
* @property TicketTypeEnum $type
|
||||
* @property string $title Titre du ticket
|
||||
* @property string $text
|
||||
* @property string|null $file
|
||||
* @property string|null $file2
|
||||
* @property string|null $file3 Pièce jointes n°3
|
||||
* @property string|null $file4 Pièce jointes n°4
|
||||
* @property string|null $file5 Pièce jointes n°5
|
||||
* @property TicketStatusEnum $status
|
||||
* @property int $order Ticket order.
|
||||
* @property bool $is_internal
|
||||
* @property string|null $gitlab_issue URL de l'issue Gitlab
|
||||
* @property \Illuminate\Support\Carbon|null $date_mep Date MEP souhaitée
|
||||
* @property int $last_activity Last activity on the ticket.
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property-read \App\Models\User|null $assignedUser
|
||||
* @property-read Estimation|null $estimation
|
||||
* @property-read string $full_name
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Label> $labels
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Ticket> $linkedTickets
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Message> $messages
|
||||
* @property-read \App\Models\User|null $projectManager
|
||||
* @property-read \App\Models\Site|null $site
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $subscriptions
|
||||
* @property-read TicketType|null $ticketType
|
||||
* @property-read \App\Models\User|null $user
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Ticket onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Ticket withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Ticket withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Ticket extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property TicketTypeEnum $type
|
||||
* @property string $template
|
||||
* @property int $id
|
||||
* @property TicketTypeEnum $type TicketType Enum
|
||||
* @property string $template Le template du type de ticket
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Ticket> $tickets
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class TicketType extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,12 +15,27 @@ use Illuminate\Support\Facades\Hash;
|
|||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $email
|
||||
* @property string $password
|
||||
* @property string $remember_token
|
||||
* @property RoleEnum $role
|
||||
* @property Collection $sites
|
||||
* @property string|null $remember_token
|
||||
* @property int|null $gitlab_id ID du user Gitlab
|
||||
* @property int|null $favourite_site_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property-read Collection<int, \App\Models\Message> $messages
|
||||
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
|
||||
* @property-read Collection<int, \App\Models\Site> $sites
|
||||
* @property-read Collection<int, \App\Models\Ticket> $subscriptions
|
||||
* @property-read Collection<int, \App\Models\Ticket> $tickets
|
||||
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User extends Authenticatable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -287,7 +287,8 @@ class TicketPolicy
|
|||
$ticket->type->equals(TicketTypeEnum::bug)
|
||||
|| $ticket->type->equals(TicketTypeEnum::evolution)
|
||||
|| $ticket->type->equals(TicketTypeEnum::newsletter)
|
||||
)
|
||||
|| $ticket->type->equals(TicketTypeEnum::mailing)
|
||||
)
|
||||
&& !\is_null($ticket->site->gitlab_project_id)
|
||||
&& \is_null($ticket->gitlab_issue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,24 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Repositories\EloquentSiteRepository;
|
||||
use App\Repositories\SiteRepository;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Override;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
#[Override]
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton(
|
||||
SiteRepository::class,
|
||||
fn() => new EloquentSiteRepository()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
|
|
@ -24,7 +36,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
return request()->getClientIp();
|
||||
} else {
|
||||
return request()->server('HTTP_X_FORWARDED_FOR') ??
|
||||
request()->getClientIp();
|
||||
request()->getClientIp();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,6 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Events\NotifyAdmin;
|
||||
use App\Events\NotifySubscriptions;
|
||||
use App\Events\NotifyTicketCreator;
|
||||
use App\Events\TicketStatusChanged;
|
||||
use App\Listeners\AddStatusMessages;
|
||||
use App\Listeners\SendNotificationMailToAdmin;
|
||||
use App\Listeners\SendNotificationMailToTicketCreator;
|
||||
use App\Listeners\SendSubscriptionsNotificationsMail;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
|
@ -23,10 +15,6 @@ class EventServiceProvider extends ServiceProvider
|
|||
*/
|
||||
protected $listen = [
|
||||
Registered::class => [SendEmailVerificationNotification::class],
|
||||
TicketStatusChanged::class => [AddStatusMessages::class],
|
||||
NotifySubscriptions::class => [SendSubscriptionsNotificationsMail::class],
|
||||
NotifyTicketCreator::class => [SendNotificationMailToTicketCreator::class],
|
||||
NotifyAdmin::class => [SendNotificationMailToAdmin::class],
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -36,6 +24,6 @@ class EventServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function shouldDiscoverEvents(): bool
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
app/Repositories/EloquentSiteRepository.php
Normal file
19
app/Repositories/EloquentSiteRepository.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Site;
|
||||
use Override;
|
||||
|
||||
class EloquentSiteRepository implements SiteRepository
|
||||
{
|
||||
#[Override]
|
||||
public function findByName(string $name): ?Site
|
||||
{
|
||||
return Site::query()
|
||||
->where('name', $name)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
12
app/Repositories/SiteRepository.php
Normal file
12
app/Repositories/SiteRepository.php
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Site;
|
||||
|
||||
interface SiteRepository
|
||||
{
|
||||
public function findByName(string $name): ?Site;
|
||||
}
|
||||
56
app/Services/AutomaticTicketCreator.php
Normal file
56
app/Services/AutomaticTicketCreator.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\Tickets\TicketStatusEnum;
|
||||
use App\Enums\Tickets\TicketTypeEnum;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
|
||||
class AutomaticTicketCreator
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BrevoWebookParser $parser
|
||||
) {}
|
||||
|
||||
public function createTicket(
|
||||
Site $forSite,
|
||||
User $withProjectManager,
|
||||
string $withRawHtmlContent,
|
||||
): Ticket {
|
||||
|
||||
$parsedContent = $this->parser->parseRawHtml($withRawHtmlContent);
|
||||
|
||||
$textContent = "Bonjour,\n";
|
||||
$textContent .= " Contenu :\n\n";
|
||||
$textContent .= " {$parsedContent->plainTextContent},\n\n";
|
||||
$textContent .= " Cordialement.";
|
||||
|
||||
$title = "Demande de mail en masse";
|
||||
|
||||
if (!empty($parsedContent->brand)) {
|
||||
$title .= " pour {$parsedContent->brand}";
|
||||
}
|
||||
|
||||
$ticket = new Ticket([
|
||||
'site_id' => $forSite->id,
|
||||
'type' => TicketTypeEnum::mailing->value,
|
||||
'status' => TicketStatusEnum::estimateTodo->value,
|
||||
'date_mep' => null,
|
||||
'text' => $textContent,
|
||||
'title' => $title,
|
||||
'is_internal' => false
|
||||
]);
|
||||
|
||||
$ticket->site()->associate($forSite);
|
||||
$ticket->user()->associate($withProjectManager);
|
||||
$ticket->assignedUser()->associate($withProjectManager);
|
||||
|
||||
$ticket->saveOrFail();
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
}
|
||||
35
app/Services/BrevoWebookParser.php
Normal file
35
app/Services/BrevoWebookParser.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Dto\BrevoWebHookDto;
|
||||
use App\Exceptions\BrevoParseFailure;
|
||||
|
||||
class BrevoWebookParser
|
||||
{
|
||||
/** @throws BrevoParseFailure */
|
||||
public function parseRawHtml(string $rawHtml): BrevoWebHookDto
|
||||
{
|
||||
|
||||
$data = [];
|
||||
$plainText = strip_tags($rawHtml);
|
||||
$tblRegexPatterns = [
|
||||
'user' => '/^Matricule et Nom du demandeur\s*\r?\n(.*)$/im',
|
||||
'email' => '/^Email du demandeur\s*\r?\n(.*)$/im',
|
||||
'brand' => '/^Marque\s*\/\s*Filiale\s*\r?\n(.*)$/im',
|
||||
];
|
||||
|
||||
foreach ($tblRegexPatterns as $field => $regex) {
|
||||
if (preg_match($regex, $plainText, $matches)) {
|
||||
$data[$field] = trim($matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return new BrevoWebHookDto(
|
||||
plainTextContent: $plainText,
|
||||
brand: $data['brand'] ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ trait HasGitlabIssue
|
|||
*/
|
||||
public function getGitlabIssue(int $projectId, string $issueUrl): array
|
||||
{
|
||||
if (!preg_match('/\/issues\/(\d+)/', $issueUrl, $matches)) { return []; }
|
||||
if (!preg_match('/\/(?:issues|work_items)\/(\d+)/', $issueUrl, $matches)) { return []; }
|
||||
|
||||
$gitlabIssue = GitLab::issues()->show($projectId, (int) $matches[1]);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"php": "^8.5",
|
||||
"carlos-meneses/laravel-mpdf": "^2.1",
|
||||
"codegreencreative/laravel-samlidp": "^5.2",
|
||||
"diglactic/laravel-breadcrumbs": "^9.0",
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
"uwil/k8s-laravel": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^3.7",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel-lang/common": "^4.0",
|
||||
"laravel/pint": "^1.0",
|
||||
|
|
@ -43,7 +44,8 @@
|
|||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
"Database\\Seeders\\": "database/seeders/",
|
||||
"Domain\\": "domain/"
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
|
|
@ -67,7 +69,11 @@
|
|||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
|
||||
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
|
||||
"@php artisan lang:update",
|
||||
"@php artisan ide-helper:generate",
|
||||
"@php artisan ide-helper:models -RW"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
|
|
|
|||
2951
composer.lock
generated
2951
composer.lock
generated
File diff suppressed because it is too large
Load diff
379
config/ide-helper.php
Normal file
379
config/ide-helper.php
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Client\Factory;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Session\Store;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filename
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The default filename.
|
||||
|
|
||||
*/
|
||||
|
||||
'filename' => '_ide_helper.php',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Models filename
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The default filename for the models helper file.
|
||||
|
|
||||
*/
|
||||
|
||||
'models_filename' => '_ide_helper_models.php',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| PhpStorm meta filename
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| PhpStorm also supports the directory `.phpstorm.meta.php/` with arbitrary
|
||||
| files in it, should you need additional files for your project; e.g.
|
||||
| `.phpstorm.meta.php/laravel_ide_Helper.php'.
|
||||
|
|
||||
*/
|
||||
'meta_filename' => '.phpstorm.meta.php',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fluent helpers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set to true to generate commonly used Fluent methods.
|
||||
|
|
||||
*/
|
||||
|
||||
'include_fluent' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Write model query methods
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set to false to disable generated docs for the 'query()', 'newQuery()' and 'newModelQuery()' methods.
|
||||
|
|
||||
*/
|
||||
|
||||
'write_query_methods' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Write model magic methods
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set to false to disable write magic methods of model.
|
||||
|
|
||||
*/
|
||||
|
||||
'write_model_magic_where' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Write model external Eloquent builder methods
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set to false to disable write external Eloquent builder methods.
|
||||
|
|
||||
*/
|
||||
|
||||
'write_model_external_builder_methods' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Write model relation count and exists properties
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set to false to disable writing of relation count and exists properties
|
||||
| to model DocBlocks.
|
||||
|
|
||||
*/
|
||||
|
||||
'write_model_relation_count_properties' => false,
|
||||
'write_model_relation_exists_properties' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Write Eloquent model mixins
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This will add the necessary DocBlock mixins to the model class
|
||||
| contained in the Laravel framework. This helps the IDE with
|
||||
| auto-completion.
|
||||
|
|
||||
| Please be aware that this setting changes a file within the /vendor directory.
|
||||
|
|
||||
*/
|
||||
|
||||
'write_eloquent_model_mixins' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Helper files to include
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Include helper files. By default not included, but can be toggled with the
|
||||
| -- helpers (-H) option. Extra helper files can be included.
|
||||
|
|
||||
*/
|
||||
|
||||
'include_helpers' => false,
|
||||
|
||||
'helper_files' => [
|
||||
base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php',
|
||||
base_path() . '/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Model locations to include
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Define in which directories the ide-helper:models command should look
|
||||
| for models.
|
||||
|
|
||||
| glob patterns are supported to easier reach models in sub-directories,
|
||||
| e.g. `app/Services/* /Models` (without the space).
|
||||
|
|
||||
*/
|
||||
|
||||
'model_locations' => [
|
||||
'app',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Models to ignore
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Define which models should be ignored.
|
||||
|
|
||||
*/
|
||||
|
||||
'ignored_models' => [],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Models hooks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Define which hook classes you want to run for models to add custom information.
|
||||
|
|
||||
| Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface.
|
||||
|
|
||||
*/
|
||||
|
||||
'model_hooks' => [
|
||||
// App\Support\IdeHelper\MyModelHook::class
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Extra classes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These implementations are not really extended, but called with magic functions.
|
||||
|
|
||||
*/
|
||||
|
||||
'extra' => [
|
||||
'Eloquent' => [Builder::class, \Illuminate\Database\Query\Builder::class],
|
||||
'Session' => [Store::class],
|
||||
],
|
||||
|
||||
'magic' => [],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Interface implementations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These interfaces will be replaced with the implementing class. Some interfaces
|
||||
| are detected by the helpers, others can be listed below.
|
||||
|
|
||||
*/
|
||||
|
||||
'interfaces' => [
|
||||
// App\MyInterface::class => App\MyImplementation::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Support for camel cased models
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| There are some Laravel packages (such as Eloquence) that allow for accessing
|
||||
| Eloquent model properties via camel case, instead of snake case.
|
||||
|
|
||||
| Enabling this option will support these packages by saving all model
|
||||
| properties as camel case, instead of snake case.
|
||||
|
|
||||
| For example, normally you would see this:
|
||||
|
|
||||
| * @property \Illuminate\Support\Carbon $created_at
|
||||
| * @property \Illuminate\Support\Carbon $updated_at
|
||||
|
|
||||
| With this enabled, the properties will be this:
|
||||
|
|
||||
| * @property \Illuminate\Support\Carbon $createdAt
|
||||
| * @property \Illuminate\Support\Carbon $updatedAt
|
||||
|
|
||||
| Note, it is currently an all-or-nothing option.
|
||||
|
|
||||
*/
|
||||
'model_camel_case_properties' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Property casts
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Cast the given "real type" to the given "type".
|
||||
|
|
||||
*/
|
||||
'type_overrides' => [
|
||||
'integer' => 'int',
|
||||
'boolean' => 'bool',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Include DocBlocks from classes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Include DocBlocks from classes to allow additional code inspection for
|
||||
| magic methods and properties.
|
||||
|
|
||||
*/
|
||||
'include_class_docblocks' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Force FQN usage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Use the fully qualified (class) name in DocBlocks,
|
||||
| even if the class exists in the same namespace
|
||||
| or there is an import (use className) of the class.
|
||||
|
|
||||
*/
|
||||
'force_fqn' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Use generics syntax
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Use generics syntax within DocBlocks,
|
||||
| e.g. `Collection<User>` instead of `Collection|User[]`.
|
||||
|
|
||||
*/
|
||||
'use_generics_annotations' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default return types for macros
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Define default return types for macros without explicit return types.
|
||||
| e.g. `\Illuminate\Database\Query\Builder::class => 'static'`,
|
||||
| `\Illuminate\Support\Str::class => 'string'`
|
||||
|
|
||||
*/
|
||||
'macro_default_return_types' => [
|
||||
Factory::class => PendingRequest::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Additional relation types
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Sometimes it's needed to create custom relation types. The key of the array
|
||||
| is the relationship method name. The value of the array is the fully-qualified
|
||||
| class name of the relationship, e.g. `'relationName' => RelationShipClass::class`.
|
||||
|
|
||||
*/
|
||||
'additional_relation_types' => [],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Additional relation return types
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using custom relation types its possible for the class name to not contain
|
||||
| the proper return type of the relation. The key of the array is the relationship
|
||||
| method name. The value of the array is the return type of the relation ('many'
|
||||
| or 'morphTo').
|
||||
| e.g. `'relationName' => 'many'`.
|
||||
|
|
||||
*/
|
||||
'additional_relation_return_types' => [],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Enforce nullable Eloquent relationships on not null columns
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When set to true (default), this option enforces nullable Eloquent relationships.
|
||||
| However, in cases where the application logic ensures the presence of related
|
||||
| records it may be desirable to set this option to false to avoid unwanted null warnings.
|
||||
|
|
||||
| Default: true
|
||||
| A not null column with no foreign key constraint will have a "nullable" relationship.
|
||||
| * @property int $not_null_column_with_no_foreign_key_constraint
|
||||
| * @property-read BelongsToVariation|null $notNullColumnWithNoForeignKeyConstraint
|
||||
|
|
||||
| Option: false
|
||||
| A not null column with no foreign key constraint will have a "not nullable" relationship.
|
||||
| * @property int $not_null_column_with_no_foreign_key_constraint
|
||||
| * @property-read BelongsToVariation $notNullColumnWithNoForeignKeyConstraint
|
||||
|
|
||||
*/
|
||||
|
||||
'enforce_nullable_relationships' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Make soft deletable relations nullable
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When set to true (default), relationships to models using SoftDeletes trait
|
||||
| will be marked as nullable. This is because soft-deleted records are excluded
|
||||
| from queries by default, meaning even non-nullable foreign keys can return
|
||||
| null when the related model is soft-deleted.
|
||||
|
|
||||
| Default: true
|
||||
| A relationship to a soft-deletable model will include |null in the type:
|
||||
| * @property-read Team|null $team
|
||||
|
|
||||
| Option: false
|
||||
| A relationship to a soft-deletable model will NOT include |null (unless
|
||||
| nullable for other reasons such as nullable foreign key column):
|
||||
| * @property-read Team $team
|
||||
|
|
||||
*/
|
||||
|
||||
'soft_deletes_force_nullable' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Run artisan commands after migrations to generate model helpers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The specified commands should run after migrations are finished running.
|
||||
|
|
||||
*/
|
||||
'post_migrate' => [
|
||||
// 'ide-helper:models --nowrite',
|
||||
],
|
||||
|
||||
];
|
||||
|
|
@ -18,6 +18,10 @@ return [
|
|||
// * Mail admin
|
||||
'MAIL_ADMIN' => env('MAIL_ADMIN', 'digital@ldc.fr'),
|
||||
|
||||
'brevo_webook' => [
|
||||
'expected_from_address' => 'mailing@reply.uwil.fr'
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|
|
|
|||
46
database/factories/SiteFactory.php
Normal file
46
database/factories/SiteFactory.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class SiteFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = Site::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->company(),
|
||||
'url_re7' => fake()->url(),
|
||||
'url_prod' => fake()->url(),
|
||||
'gitlab_project_id' => fake()->optional()->randomNumber(4),
|
||||
'brand_id' => null,
|
||||
'project_manager_id' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indique que le site a un chef de projet assigné.
|
||||
*/
|
||||
public function withProjectManager(): static
|
||||
{
|
||||
return $this->state(fn(array $attributes) => [
|
||||
'project_manager_id' => User::factory(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
|
|
@ -18,17 +20,23 @@ class UserFactory extends Factory
|
|||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition()
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->name,
|
||||
'email' => $this->faker->unique()->safeEmail,
|
||||
'email_verified_at' => now(),
|
||||
// * password
|
||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
|
||||
'remember_token' => Str::random(10),
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'role' => RoleEnum::developper,
|
||||
'gitlab_id' => fake()->optional()->randomNumber(5),
|
||||
'favourite_site_id' => null,
|
||||
];
|
||||
}
|
||||
|
||||
public function admin(): static
|
||||
{
|
||||
return $this->state(fn(array $attributes) => [
|
||||
'role' => RoleEnum::admin,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ class CreateCreditLogsTable extends Migration
|
|||
Schema::create('credit_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedTinyInteger('type')->comment('Enum Credit\\BalanceOperationEnum');
|
||||
$table->unsignedFloat('amount')->comment('Montant de l\'opération');
|
||||
$table->tinyInteger('type')->unsigned()->comment('Enum Credit\\BalanceOperationEnum');
|
||||
$table->float('amount')->unsigned()->comment('Montant de l\'opération');
|
||||
|
||||
$table->foreignId('credit_account_id')
|
||||
->constrained('credit_accounts')
|
||||
->cascadeOnDelete();
|
||||
->constrained('credit_accounts')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->timestamp('created_at');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,24 +18,24 @@ class CreateCreditEstimationsTable extends Migration
|
|||
Schema::create('credit_estimations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedTinyInteger('status')->default(EstimationStatusEnum::pending)
|
||||
->comment('Enum Credit\\EstimationStatusEnum');
|
||||
$table->tinyInteger('status')->unsigned()->default(EstimationStatusEnum::pending)
|
||||
->comment('Enum Credit\\EstimationStatusEnum');
|
||||
|
||||
$table->unsignedFloat('amount')->comment('Estimation amount');
|
||||
$table->float('amount')->unsigned()->comment('Estimation amount');
|
||||
|
||||
$table->foreignIdFor(Ticket::class)
|
||||
->constrained();
|
||||
|
||||
$table->foreignId('credit_account_id')
|
||||
->comment('The account to debit.')
|
||||
->constrained('credit_accounts');
|
||||
->comment('The account to debit.')
|
||||
->constrained('credit_accounts');
|
||||
$table->foreignId('estimator_id')
|
||||
->comment('The person who has done the estimate.')
|
||||
->constrained('users');
|
||||
->comment('The person who has done the estimate.')
|
||||
->constrained('users');
|
||||
$table->foreignId('validator_id')
|
||||
->nullable()
|
||||
->comment('The person who has validate the estimate.')
|
||||
->constrained('users');
|
||||
->nullable()
|
||||
->comment('The person who has validate the estimate.')
|
||||
->constrained('users');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ChangeAmoutFloatingPointOnCreditEstimations extends Migration
|
||||
|
|
@ -12,8 +13,8 @@ class ChangeAmoutFloatingPointOnCreditEstimations extends Migration
|
|||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('credit_estimations', function ($table) {
|
||||
$table->unsignedFloat('amount', 8, 3)
|
||||
Schema::table('credit_estimations', function (Blueprint $table) {
|
||||
$table->decimal('amount', 8, 3)->unsigned()
|
||||
->comment('Estimation amount')->change();
|
||||
});
|
||||
}
|
||||
|
|
@ -25,8 +26,8 @@ class ChangeAmoutFloatingPointOnCreditEstimations extends Migration
|
|||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('credit_estimations', function ($table) {
|
||||
$table->unsignedFloat('amount')
|
||||
Schema::table('credit_estimations', function (Blueprint $table) {
|
||||
$table->float('amount')->unsigned()
|
||||
->comment('Estimation amount')->change();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class CreatePackagesTable extends Migration
|
|||
$table->id();
|
||||
|
||||
$table->string('name');
|
||||
$table->unsignedFloat('amount')->comment('The cost per day');
|
||||
$table->float('amount')->unsigned()->comment('The cost per day');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ class CreateInvoicesTable extends Migration
|
|||
'A JSON object, each line has to be contructed using these fields description, days, type Invoices\TaskTypeEnum, amount'
|
||||
);
|
||||
|
||||
$table->unsignedFloat('total')->comment('The total calculated using all tasks');
|
||||
$table->unsignedFloat('total_days')->comment('The total days calculated using all tasks');
|
||||
$table->float('total')->unsigned()->comment('The total calculated using all tasks');
|
||||
$table->float('total_days')->unsigned()->comment('The total days calculated using all tasks');
|
||||
|
||||
$table->text('comment')->nullable()
|
||||
->comment('Any comment joined to the invoice');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class ChangeTicketsStatus extends Migration
|
||||
|
|
@ -12,17 +12,22 @@ class ChangeTicketsStatus extends Migration
|
|||
*/
|
||||
public function up()
|
||||
{
|
||||
Ticket::whereIn('status', [4, 5, 7, 10])
|
||||
DB::table('tickets')->whereIn('status', [4, 5, 7, 10])
|
||||
->update(['status' => 3]);
|
||||
Ticket::where('status', 9)
|
||||
|
||||
DB::table('tickets')->where('status', 9)
|
||||
->update(['status' => 4]);
|
||||
Ticket::where('status', 17)
|
||||
|
||||
DB::table('tickets')->where('status', 17)
|
||||
->update(['status' => 5]);
|
||||
Ticket::where('status', 12)
|
||||
|
||||
DB::table('tickets')->where('status', 12)
|
||||
->update(['status' => 6]);
|
||||
Ticket::where('status', 13)
|
||||
|
||||
DB::table('tickets')->where('status', 13)
|
||||
->update(['status' => 7]);
|
||||
Ticket::where('status', 15)
|
||||
|
||||
DB::table('tickets')->where('status', 15)
|
||||
->update(['status' => 8]);
|
||||
}
|
||||
|
||||
|
|
@ -33,17 +38,22 @@ class ChangeTicketsStatus extends Migration
|
|||
*/
|
||||
public function down()
|
||||
{
|
||||
Ticket::where('status', 3)
|
||||
DB::table('tickets')->where('status', 3)
|
||||
->update(['status' => 10]);
|
||||
Ticket::where('status', 4)
|
||||
|
||||
DB::table('tickets')->where('status', 4)
|
||||
->update(['status' => 9]);
|
||||
Ticket::where('status', 5)
|
||||
|
||||
DB::table('tickets')->where('status', 5)
|
||||
->update(['status' => 17]);
|
||||
Ticket::where('status', 6)
|
||||
|
||||
DB::table('tickets')->where('status', 6)
|
||||
->update(['status' => 12]);
|
||||
Ticket::where('status', 7)
|
||||
|
||||
DB::table('tickets')->where('status', 7)
|
||||
->update(['status' => 13]);
|
||||
Ticket::where('status', 8)
|
||||
|
||||
DB::table('tickets')->where('status', 8)
|
||||
->update(['status' => 15]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Ticket;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
|
|
@ -17,15 +16,32 @@ return new class extends Migration
|
|||
{
|
||||
Schema::table('tickets', function (Blueprint $table) {
|
||||
$table->timestamp('last_activity')
|
||||
->default(Carbon::now())
|
||||
->useCurrent()
|
||||
->comment('Last activity on the ticket.')
|
||||
->after('is_internal');
|
||||
});
|
||||
Ticket::get()->map(function (object $ticket) {
|
||||
$lastMessage = $ticket->messages->whereNotNull('user_id')->sortByDesc('updated_at')->first();
|
||||
$ticket->last_activity =
|
||||
$lastMessage->updated_at ?? $ticket->updated_at;
|
||||
$ticket->saveQuietly();
|
||||
|
||||
DB::table('tickets')->orderBy('id')->chunk(500, function ($tickets) {
|
||||
$ticketIds = $tickets->pluck('id')->toArray();
|
||||
|
||||
$messages = DB::table('messages')
|
||||
->whereIn('ticket_id', $ticketIds)
|
||||
->whereNotNull('user_id')
|
||||
->orderBy('updated_at', 'desc')
|
||||
->get()
|
||||
->groupBy('ticket_id');
|
||||
|
||||
foreach ($tickets as $ticket) {
|
||||
$lastMessageDate = $messages->has($ticket->id)
|
||||
? $messages->get($ticket->id)->first()->updated_at
|
||||
: null;
|
||||
|
||||
DB::table('tickets')
|
||||
->where('id', $ticket->id)
|
||||
->update([
|
||||
'last_activity' => $lastMessageDate ?? $ticket->updated_at
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
|
|
@ -22,11 +21,15 @@ return new class extends Migration
|
|||
});
|
||||
|
||||
DB::transaction(function () {
|
||||
$tickets = Ticket::all();
|
||||
foreach ($tickets as $ticket) {
|
||||
$ticket->title = 'ts-' . $ticket->id;
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
DB::table('tickets')->orderBy('id')->chunkById(1000, function ($tickets) {
|
||||
foreach ($tickets as $ticket) {
|
||||
DB::table('tickets')
|
||||
->where('id', $ticket->id)
|
||||
->update([
|
||||
'title' => 'ts-' . $ticket->id
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Ticket;
|
||||
use App\Models\Ticket\TicketType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
|
|
@ -13,13 +12,19 @@ return new class extends Migration
|
|||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Ticket::query()->get()->map(function (Ticket $model) {
|
||||
$model->type = $model->getRawOriginal('type') + 1;
|
||||
$model->saveQuietly();
|
||||
});
|
||||
TicketType::query()->orderBy('type', 'DESC')->get()->map(function (TicketType $model) {
|
||||
$model->type = $model->getRawOriginal('type') + 1;
|
||||
$model->saveQuietly();
|
||||
DB::transaction(function () {
|
||||
DB::table('tickets')->orderBy('id')->chunkById(1000, function ($tickets) {
|
||||
$ids = $tickets->pluck('id')->toArray();
|
||||
DB::table('tickets')->whereIn('id', $ids)->increment('type');
|
||||
});
|
||||
|
||||
$ticketTypes = DB::table('ticket_types')->orderBy('type', 'desc')->get();
|
||||
|
||||
foreach ($ticketTypes as $type) {
|
||||
DB::table('ticket_types')
|
||||
->where('id', $type->id)
|
||||
->update(['type' => $type->type + 1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -30,13 +35,20 @@ return new class extends Migration
|
|||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Ticket::query()->get()->map(function (Ticket $model) {
|
||||
$model->type = $model->getRawOriginal('type') - 1;
|
||||
$model->saveQuietly();
|
||||
});
|
||||
TicketType::query()->orderBy('type', 'DESC')->get()->map(function (TicketType $model) {
|
||||
$model->type = $model->getRawOriginal('type') - 1;
|
||||
$model->saveQuietly();
|
||||
DB::transaction(function () {
|
||||
// 1. Restauration des tickets
|
||||
DB::table('tickets')->orderBy('id')->chunkById(1000, function ($tickets) {
|
||||
$ids = $tickets->pluck('id')->toArray();
|
||||
DB::table('tickets')->whereIn('id', $ids)->decrement('type');
|
||||
});
|
||||
|
||||
$ticketTypes = DB::table('ticket_types')->orderBy('type', 'asc')->get();
|
||||
|
||||
foreach ($ticketTypes as $type) {
|
||||
DB::table('ticket_types')
|
||||
->where('id', $type->id)
|
||||
->update(['type' => $type->type - 1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
|
|
@ -12,10 +12,9 @@ return new class extends Migration
|
|||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Ticket::query()->where('status', '>', 6)->get()->map(function (Ticket $model) {
|
||||
$model->status = $model->getRawOriginal('status') + 1;
|
||||
$model->saveQuietly();
|
||||
});
|
||||
DB::table('tickets')
|
||||
->where('status', '>', 6)
|
||||
->increment('status');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -25,9 +24,8 @@ return new class extends Migration
|
|||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Ticket::query()->where('status', '>', 6)->get()->map(function (Ticket $model) {
|
||||
$model->status = $model->getRawOriginal('status') - 1;
|
||||
$model->saveQuietly();
|
||||
});
|
||||
DB::table('tickets')
|
||||
->where('status', '>', 6)
|
||||
->decrement('status');
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use App\Models\Site;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
|
|
@ -15,14 +15,31 @@ return new class extends Migration
|
|||
public function up(): void
|
||||
{
|
||||
Schema::table('tickets', function (Blueprint $table) {
|
||||
$table->integer('order')->after('status')->comment('Ticket order.');
|
||||
$table->integer('order')->default(0)->after('status')->comment('Ticket order.');
|
||||
});
|
||||
|
||||
Site::query()->get()->map(function ($site) {
|
||||
$site->tickets->map(function ($ticket, $ticketIndex) {
|
||||
$ticket->order = $ticketIndex + 1;
|
||||
$ticket->saveQuietly();
|
||||
});
|
||||
DB::transaction(function () {
|
||||
$currentSiteId = null;
|
||||
$orderCounter = 1;
|
||||
|
||||
DB::table('tickets')
|
||||
->orderBy('site_id')
|
||||
->orderBy('id')
|
||||
->chunk(1000, function ($tickets) use (&$currentSiteId, &$orderCounter) {
|
||||
|
||||
foreach ($tickets as $ticket) {
|
||||
if ($ticket->site_id !== $currentSiteId) {
|
||||
$currentSiteId = $ticket->site_id;
|
||||
$orderCounter = 1;
|
||||
}
|
||||
|
||||
DB::table('tickets')
|
||||
->where('id', $ticket->id)
|
||||
->update(['order' => $orderCounter]);
|
||||
|
||||
$orderCounter++;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
21
domain/Models/ValueObjects/Email.php
Normal file
21
domain/Models/ValueObjects/Email.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects;
|
||||
|
||||
final readonly class Email
|
||||
{
|
||||
public function __construct(
|
||||
public string $value
|
||||
) {
|
||||
$this->validate($value);
|
||||
}
|
||||
|
||||
private function validate(string $email): void
|
||||
{
|
||||
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \DomainException('Invalid email address');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,9 +75,9 @@ spec:
|
|||
imagePullSecrets:
|
||||
- name: docker-registry-credentials
|
||||
securityContext:
|
||||
runAsUser: 33
|
||||
runAsGroup: 33
|
||||
fsGroup: 33
|
||||
runAsUser: 82
|
||||
runAsGroup: 82
|
||||
fsGroup: 82
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
volumes:
|
||||
- name: storage
|
||||
|
|
@ -161,8 +161,8 @@ spec:
|
|||
persistentVolumeClaim:
|
||||
claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage
|
||||
securityContext:
|
||||
runAsUser: 33
|
||||
runAsGroup: 33
|
||||
fsGroup: 33
|
||||
runAsUser: 82
|
||||
runAsGroup: 82
|
||||
fsGroup: 82
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
restartPolicy: OnFailure
|
||||
|
|
|
|||
188
package-lock.json
generated
188
package-lock.json
generated
|
|
@ -4,6 +4,9 @@
|
|||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"meow": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eastdesire/jscolor": "^2.5.1",
|
||||
"@fortawesome/fontawesome-svg-core": "latest",
|
||||
|
|
@ -76,7 +79,6 @@
|
|||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
|
|
@ -416,7 +418,6 @@
|
|||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
|
|
@ -462,7 +463,6 @@
|
|||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
|
|
@ -2542,9 +2542,7 @@
|
|||
"node_modules/@types/minimist": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz",
|
||||
"integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.8.4",
|
||||
|
|
@ -2558,9 +2556,7 @@
|
|||
"node_modules/@types/normalize-package-data": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz",
|
||||
"integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A=="
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.3",
|
||||
|
|
@ -3222,7 +3218,6 @@
|
|||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
|
|
@ -3262,8 +3257,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
"integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -3447,9 +3440,7 @@
|
|||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
|
@ -3458,9 +3449,7 @@
|
|||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
|
||||
"integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.3.1",
|
||||
"map-obj": "^4.0.0",
|
||||
|
|
@ -3497,7 +3486,6 @@
|
|||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
|
|
@ -3562,7 +3550,6 @@
|
|||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
|
|
@ -3570,8 +3557,7 @@
|
|||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
|
||||
},
|
||||
"node_modules/colord": {
|
||||
"version": "2.9.3",
|
||||
|
|
@ -3750,8 +3736,6 @@
|
|||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -3760,8 +3744,6 @@
|
|||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"decamelize": "^1.1.0",
|
||||
"map-obj": "^1.0.0"
|
||||
|
|
@ -3777,8 +3759,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
|
||||
"integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -3839,8 +3819,6 @@
|
|||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
|
|
@ -3896,7 +3874,6 @@
|
|||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
|
|
@ -4544,8 +4521,6 @@
|
|||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
|
||||
"integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
|
@ -4554,7 +4529,6 @@
|
|||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
|
||||
"integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
|
|
@ -4563,7 +4537,6 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
|
|
@ -4581,8 +4554,6 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
||||
"integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
|
|
@ -4594,8 +4565,6 @@
|
|||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
|
|
@ -4606,9 +4575,7 @@
|
|||
"node_modules/hosted-git-info/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/html-tags": {
|
||||
"version": "3.3.1",
|
||||
|
|
@ -4677,9 +4644,7 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
||||
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -4710,9 +4675,7 @@
|
|||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
|
|
@ -4730,7 +4693,6 @@
|
|||
"version": "2.13.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
|
||||
"integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has": "^1.0.3"
|
||||
},
|
||||
|
|
@ -4791,8 +4753,6 @@
|
|||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||
"integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -4816,8 +4776,7 @@
|
|||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
|
|
@ -4852,9 +4811,7 @@
|
|||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
|
|
@ -4905,8 +4862,6 @@
|
|||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -4949,9 +4904,7 @@
|
|||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
|
|
@ -5024,8 +4977,6 @@
|
|||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
|
||||
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
|
|
@ -5055,9 +5006,7 @@
|
|||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
|
||||
"integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/minimist": "^1.2.0",
|
||||
"camelcase-keys": "^6.2.2",
|
||||
|
|
@ -5083,9 +5032,7 @@
|
|||
"version": "0.18.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
|
||||
"integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
|
|
@ -5140,8 +5087,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
|
|
@ -5162,8 +5107,6 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
|
||||
"integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"arrify": "^1.0.1",
|
||||
"is-plain-obj": "^1.1.0",
|
||||
|
|
@ -5228,8 +5171,6 @@
|
|||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
|
||||
"integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"hosted-git-info": "^4.0.1",
|
||||
"is-core-module": "^2.5.0",
|
||||
|
|
@ -5244,8 +5185,6 @@
|
|||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
|
|
@ -5257,8 +5196,6 @@
|
|||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
|
|
@ -5272,9 +5209,7 @@
|
|||
"node_modules/normalize-package-data/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
|
|
@ -5369,9 +5304,7 @@
|
|||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
|
@ -5392,8 +5325,6 @@
|
|||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"error-ex": "^1.3.1",
|
||||
|
|
@ -5411,7 +5342,6 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -5437,8 +5367,7 @@
|
|||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
|
|
@ -5659,9 +5588,7 @@
|
|||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
|
||||
"integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -5670,9 +5597,7 @@
|
|||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
|
||||
"integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/normalize-package-data": "^2.4.0",
|
||||
"normalize-package-data": "^2.5.0",
|
||||
|
|
@ -5687,9 +5612,7 @@
|
|||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
|
||||
"integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"find-up": "^4.1.0",
|
||||
"read-pkg": "^5.2.0",
|
||||
|
|
@ -5706,9 +5629,7 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
|
|
@ -5721,9 +5642,7 @@
|
|||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
|
|
@ -5735,9 +5654,7 @@
|
|||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
|
|
@ -5752,9 +5669,7 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
|
|
@ -5766,9 +5681,7 @@
|
|||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
|
||||
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -5777,17 +5690,13 @@
|
|||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/read-pkg/node_modules/normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"hosted-git-info": "^2.1.4",
|
||||
"resolve": "^1.10.0",
|
||||
|
|
@ -5799,9 +5708,7 @@
|
|||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
|
|
@ -5810,9 +5717,7 @@
|
|||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
|
||||
"integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -5833,9 +5738,7 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"indent-string": "^4.0.0",
|
||||
"strip-indent": "^3.0.0"
|
||||
|
|
@ -5929,7 +5832,6 @@
|
|||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"path-parse": "^1.0.7",
|
||||
|
|
@ -6180,8 +6082,6 @@
|
|||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
|
||||
"integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
|
|
@ -6190,16 +6090,12 @@
|
|||
"node_modules/spdx-exceptions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
|
||||
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A=="
|
||||
},
|
||||
"node_modules/spdx-expression-parse": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
|
||||
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"spdx-exceptions": "^2.1.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
|
|
@ -6208,9 +6104,7 @@
|
|||
"node_modules/spdx-license-ids": {
|
||||
"version": "3.0.16",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
|
||||
"integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw=="
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
|
|
@ -6243,9 +6137,7 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"min-indent": "^1.0.0"
|
||||
},
|
||||
|
|
@ -6688,7 +6580,6 @@
|
|||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
|
|
@ -6737,7 +6628,6 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
|
|
@ -6870,9 +6760,7 @@
|
|||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
|
||||
"integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -7030,8 +6918,6 @@
|
|||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"spdx-correct": "^3.0.0",
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
|
|
@ -7656,8 +7542,6 @@
|
|||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,5 +55,8 @@
|
|||
"vue": "latest",
|
||||
"vue-select": "^4.0.0-beta.6",
|
||||
"vue-tsc": "1.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"meow": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 130 KiB |
|
|
@ -1,13 +1,13 @@
|
|||
<p>Bonjour,</p>
|
||||
<p>Un nouveau ticket (n°{{ $ticket->id }}) a été créé sur le support :</p>
|
||||
<p>Un nouveau ticket (n°{{ $ticketId }}) a été créé sur le support :</p>
|
||||
<ul>
|
||||
<li>Projet : {{ $ticket->site->name }}</li>
|
||||
<li>Titre : {{ $ticket->title }}</li>
|
||||
<li>Type : {{ $ticket->type->label() }}</li>
|
||||
<li>Créé par : {{ $ticket->user()->withTrashed()->firstOrFail()->name }}</li>
|
||||
<li>Projet : {{ $siteName }}</li>
|
||||
<li>Titre : {{ $ticketTitle }}</li>
|
||||
<li>Type : {{ $ticketType->label() }}</li>
|
||||
<li>Créé par : {{ $userName }}</li>
|
||||
</ul>
|
||||
<p>
|
||||
Description :<br>
|
||||
{!! clean(nl2br($ticket->text)) !!}
|
||||
Description :<br>
|
||||
{!! clean(nl2br($ticketDescription)) !!}
|
||||
</p>
|
||||
<p>Lien vers le ticket : <a href="{{ route('bo.ticket.show', $ticket->id) }}" traget="_blank">Ouvrir dans un nouvel onglet</a></p>
|
||||
<p>Lien vers le ticket : <a href="{{ route('bo.ticket.show', $ticketId) }}" traget="_blank">Ouvrir dans un nouvel onglet</a></p>
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ use App\Http\Controllers\Bo\PackageController;
|
|||
use App\Http\Controllers\Bo\SiteController;
|
||||
use App\Http\Controllers\Bo\TicketController;
|
||||
use App\Http\Controllers\Bo\TicketTypeController;
|
||||
use App\Http\Controllers\Bo\BrevoController;
|
||||
use App\Http\Controllers\Bo\BrevoWebhookController;
|
||||
use App\Http\Controllers\Bo\UserController;
|
||||
use App\Http\Middleware\SecureHeaders;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::post('/brevo-in', [BrevoController::class, 'createTicket']);
|
||||
Route::post('/brevo-in', [BrevoWebhookController::class, 'webhookCallReceived']);
|
||||
|
||||
Route::middleware('admin-secure')
|
||||
->group(function () {
|
||||
|
|
@ -130,7 +130,8 @@ Route::middleware('admin-secure')
|
|||
->middleware('can:estimate,ticket')
|
||||
->name('estimateSubmit');
|
||||
Route::post('/{ticket}/estimate/answer', [
|
||||
EstimationController::class, 'estimatePendingSubmit'
|
||||
EstimationController::class,
|
||||
'estimatePendingSubmit'
|
||||
])
|
||||
->middleware('can:validateEstimate,ticket')
|
||||
->name('estimatePendingSubmit');
|
||||
|
|
|
|||
|
|
@ -15,6 +15,6 @@ class ExampleTest extends TestCase
|
|||
{
|
||||
$response = $this->get('/');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testBasicTest()
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
78
tests/Unit/Services/AutomaticTicketCreatorTest.php
Normal file
78
tests/Unit/Services/AutomaticTicketCreatorTest.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Dto\BrevoWebHookDto;
|
||||
use App\Enums\Tickets\TicketStatusEnum;
|
||||
use App\Enums\Tickets\TicketTypeEnum;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
use App\Services\AutomaticTicketCreator;
|
||||
use App\Services\BrevoWebookParser;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AutomaticTicketCreatorTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private AutomaticTicketCreator $creator;
|
||||
private BrevoWebookParser|MockInterface $parserMock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->parserMock = Mockery::mock(BrevoWebookParser::class);
|
||||
$this->creator = new AutomaticTicketCreator($this->parserMock);
|
||||
}
|
||||
|
||||
public function testCanCreateTicketWithProjectManagerAndBrand(): void
|
||||
{
|
||||
// GIVEN
|
||||
$site = Site::factory()->create();
|
||||
$manager = User::factory()->create();
|
||||
$rawHtml = 'Simili HTML dynamique';
|
||||
|
||||
$dto = new BrevoWebHookDto(
|
||||
plainTextContent: 'Contenu parsé',
|
||||
brand: 'LDC Service'
|
||||
);
|
||||
|
||||
$this->parserMock
|
||||
->shouldReceive('parseRawHtml')
|
||||
->once()
|
||||
->with($rawHtml)
|
||||
->andReturn($dto);
|
||||
|
||||
// WHEN
|
||||
$ticket = $this->creator->createTicket($site, $manager, $rawHtml);
|
||||
|
||||
// THEN
|
||||
$this->assertInstanceOf(Ticket::class, $ticket);
|
||||
$this->assertEquals($site->id, $ticket->site_id);
|
||||
$this->assertEquals(TicketTypeEnum::mailing, $ticket->type);
|
||||
$this->assertEquals(TicketStatusEnum::estimateTodo, $ticket->status);
|
||||
|
||||
$this->assertEquals('Demande de mail en masse pour LDC Service', $ticket->title);
|
||||
$this->assertStringContainsString('Contenu :', $ticket->text);
|
||||
$this->assertStringContainsString('Contenu parsé', $ticket->text);
|
||||
|
||||
$this->assertFalse($ticket->is_internal);
|
||||
|
||||
$this->assertTrue($ticket->user->is($manager));
|
||||
$this->assertTrue($ticket->assignedUser->is($manager));
|
||||
|
||||
$this->assertDatabaseHas('tickets', [
|
||||
'id' => $ticket->id,
|
||||
'created_by' => $manager->id,
|
||||
'assigned_to' => $manager->id,
|
||||
'title' => 'Demande de mail en masse pour LDC Service'
|
||||
]);
|
||||
}
|
||||
}
|
||||
49
tests/Unit/Services/BrevoWebookParserTest.php
Normal file
49
tests/Unit/Services/BrevoWebookParserTest.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Dto\BrevoWebHookDto;
|
||||
use App\Services\BrevoWebookParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class BrevoWebookParserTest extends TestCase
|
||||
{
|
||||
private BrevoWebookParser $parser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->parser = new BrevoWebookParser();
|
||||
}
|
||||
|
||||
public function testCanExtractDataWithRegexUsingPipedFormat(): void
|
||||
{
|
||||
// GIVEN
|
||||
$rawHtml = <<<TEXT
|
||||
Détails de la demande :\r\n\r\nOutil envoi de mails en masse\r\n\r\n\r\nDescription\r\nReponse\r\n\r\n\r\nMatricule et Nom du demandeur\r\nK00112323 : toto jean\r\n\r\n\r\nEmail du demandeur\r\ntoto.jean@ldc.fr : Jean\r\n\r\n\r\nMarque / Filiale\r\nLDC Service\r\n\r\n\r\nPour quels besoins avez-vous besoin d'envoyer des emails en masse ?\r\nL'aigüe ambiguïté exige qu'on s'y penche. Œil, cœur, bœuf: la naïveté est un mythe. Test accents: àéèïôùç. Maman a acheté 15,99€ de pâte (soit 10%)! Symboles: & ~ # | \\ ^ @ % * $ £ ¥ ¤ + = - _ Parenthèses: (test) [test] {test} <test>. Cyrillique: Добрый день. Arabe: مرحبا. Emojis: 😀🚀🐛. Test XSS: <script>alert('XSS');</script> Test SQL: SELECT * FROM users WHERE nom = 'O\\'Brian'; DROP TABLE test; -- Fin du texte 1. 1234567890. Remplissage pour atteindre un peu moins de 500 chars.\r\n\r\n\r\nDate de première envoi souhaité\r\n30/06/2026\r\n\r\n\r\nCommentaire\r\nTest de sauts de ligne pour le second champ.\r\n\r\n\tTabulation et espaces multiples.\r\nJSON :\r\n{'id': 999, 'name': 'L'Étranger', 'tags': ['<img src=x onerror=alert(1)>']}\r\n\r\nVérification Regex :\r\nEmail: jean.dupont+test@exemple.co.uk\r\nURL: https://www.exemple.com/api/v1/data?p=1#ancre\r\nTél: +33(0)123456789\r\nIPv4: 192.168.1.254 | IPv6: 2001:db8::8a2e:370:7334\r\n\r\nCe second bloc vérifie que les données structurées et les liens survivent au transfert. Remplissage pour atteindre la limite de sept cents caractères sans la dépasser. Chinois: 这是一个测试. Japonais: こんにちは世界. Test des retours chariots invisibles. Fin du second bloc de test.\r\n\r\n\r\n
|
||||
TEXT;
|
||||
|
||||
// WHEN
|
||||
$dto = $this->parser->parseRawHtml($rawHtml);
|
||||
|
||||
// THEN
|
||||
$this->assertInstanceOf(BrevoWebHookDto::class, $dto);
|
||||
$this->assertEquals('LDC Service', $dto->brand);
|
||||
$this->assertEquals($rawHtml, $dto->plainTextContent);
|
||||
}
|
||||
|
||||
public function testHandlesMissingFieldsGracefully(): void
|
||||
{
|
||||
// GIVEN
|
||||
$rawHtml = "<p>Contenu classique sans le formalisme du tableau markdown</p>";
|
||||
|
||||
// WHEN
|
||||
$dto = $this->parser->parseRawHtml($rawHtml);
|
||||
|
||||
// THEN
|
||||
$this->assertNull($dto->brand);
|
||||
$this->assertEquals("Contenu classique sans le formalisme du tableau markdown", $dto->plainTextContent);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue