Merge branch 'develop' into trunk

This commit is contained in:
Vincent BRIDIER 2023-08-16 10:30:48 +02:00
commit f150e96d39
No known key found for this signature in database
GPG key ID: 8571BFA20420D512
44 changed files with 1475 additions and 237 deletions

View file

@ -36,8 +36,6 @@ MAIL_ADMIN=equipe.web@ldc.fr
# Sentry
SENTRY_LARAVEL_DSN=
LDC_STATUS_CHECKER_API_KEY=
# ADMIN SECURE
# - API ENDPOINT (Optional)
LDC_WEB_API_ENDPOINT=
@ -45,3 +43,7 @@ LDC_WEB_API_ENDPOINT=
LDC_WEB_API_KEY=
# - LIST SEPARATED WITH ',' (Optional)
LDC_WEB_CIDR_MANUAL_ALLOWED_LIST=
# GITLAB API
GITLAB_URL=
GITLAB_TOKEN=

View file

@ -0,0 +1,25 @@
<?php
namespace App\Enums\Invoices;
use App\Traits\Enums\BaseEnum;
enum InvoiceStatusEnum: int
{
use BaseEnum;
case opened = 1;
case refused = 2;
case canceled = 3;
case archived = 4;
/**
* Optionnal labels definition.
*/
private const LABELS = [
self::opened->name => "Ouvert (nouveaux ou en cours)",
self::refused->name => "Refusé (par le client ou aucune réponse de sa part)",
self::canceled->name => "Annulé (supprimé)",
self::archived->name => "Archivé (validé et envoyé à la comptabilité)",
];
}

View file

@ -1,6 +1,6 @@
<?php
namespace App\Enums\Invoice;
namespace App\Enums\Invoices;
use App\Traits\Enums\BaseEnum;

View file

@ -0,0 +1,20 @@
<?php
namespace App\Enums\Users;
use App\Traits\Enums\BaseEnum;
enum GitlabUsersEnum: int
{
use BaseEnum;
// Each user is associated to his ID on gitlab.
case olivier = 11;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::olivier->name => "Olivier LEGAT",
];
}

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Bo;
use App\Enums\Invoices\InvoiceStatusEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Invoices\StoreInvoiceRequest;
use App\Http\Requests\Bo\Invoices\UpdateInvoiceRequest;
@ -26,6 +27,10 @@ class InvoiceController extends Controller
{
/** @var \Illuminate\Database\Eloquent\Builder $invoiceModels */
$invoiceModels = Invoice::query();
$status = intval($request->status);
$invoiceModels = $invoiceModels->when($status, function ($query) use ($status) {
$query->where('status', $status);
});
/** @var string $search Search field */
$search = $request->search;
@ -46,7 +51,7 @@ class InvoiceController extends Controller
/** Custom pagination */
$invoiceModels = $this->customPaginate($invoiceModels, $request->pagination);
return view('bo.pages.invoices.index', compact('invoiceModels', 'search', 'searchFields'));
return view('bo.pages.invoices.index', compact('invoiceModels', 'search', 'searchFields', 'status'));
}
/**
@ -79,6 +84,7 @@ class InvoiceController extends Controller
'packageModels' => $packageModels,
'siteModels' => $siteModels,
'companyModels' => $companyModels,
'invoiceStatusArray' => $invoice->getInvoiceStatusArray($invoice),
]);
}
@ -130,6 +136,7 @@ class InvoiceController extends Controller
'packageModels' => $packageModels,
'siteModels' => $siteModels,
'companyModels' => $companyModels,
'invoiceStatusArray' => $invoice->getInvoiceStatusArray($invoice),
]);
}
@ -143,12 +150,7 @@ class InvoiceController extends Controller
public function update(UpdateInvoiceRequest $request, Invoice $invoice): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $invoice) {
if (isset($invoice->signed_date)) {
$fields = $request->safe()->only('signed_date', 'sent_client_date', 'sent_accounting_date');
} else {
$fields = $request->validated();
}
$invoice->fill($fields);
$invoice->fill(($invoice->status->value === InvoiceStatusEnum::opened->value) ? $request->validated() : []);
if ($invoice->saveOrFail()) {
return redirect()
->route('bo.invoices.edit', $invoice)

View file

@ -129,7 +129,7 @@ class SiteController extends Controller
{
return DB::transaction(function () use ($request, $site) {
$site->fill($request->validated());
$site->users()->attach(collect($request->validated('users'))->pluck('id'));
$site->users()->sync(collect($request->validated('users'))->pluck('id'));
if ($site->saveOrFail()) {
return redirect()
->route('bo.sites.edit', $site)

View file

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Bo;
use App\Enums\Tickets\TicketStatusEnum;
use App\Enums\Tickets\TicketTypeEnum;
use App\Enums\Users\GitlabUsersEnum;
use App\Events\NotifySubscriptions;
use App\Events\TicketStatusChanged;
use App\Http\Controllers\Controller;
@ -21,6 +22,7 @@ use App\Models\Message;
use App\Models\Site;
use App\Models\Ticket;
use App\Models\User;
use GrahamCampbell\GitLab\Facades\GitLab;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@ -541,7 +543,7 @@ class TicketController extends Controller
Session::get('tickets.sort_way')
);
} else {
$tickets->orderBy('id', 'DESC');
$tickets->orderBy('last_activity', 'DESC');
}
return $tickets;
}
@ -624,4 +626,82 @@ class TicketController extends Controller
);
});
}
/**
* Create a gitlab issue from a ticket.
*
* @param \App\Models\Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function createGitlabIssue(Ticket $ticket)
{
$gitlabRepoId = $ticket->site->gitlab_project_id;
$description = \sprintf("%s<br>", $ticket->text);
$cLabel = match ($ticket->type) {
TicketTypeEnum::bug => 'c-bug',
TicketTypeEnum::evolution => 'c-feature',
};
try {
if (!\is_null($ticket->file)) {
$uploadedFile = GitLab::projects()->uploadFile($gitlabRepoId, $ticket->file);
$description .= $this->formatGitlabFiles($ticket, $uploadedFile, 1);
}
if (!\is_null($ticket->file2)) {
$uploadedFile2 = GitLab::projects()->uploadFile($gitlabRepoId, $ticket->file2);
$description .= $this->formatGitlabFiles($ticket, $uploadedFile2, 2);
}
$issue = GitLab::issues()->create($gitlabRepoId, [
'title' => 'ts-' . $ticket->id,
'description' => $description,
'labels' => \implode(",", [
'c-support',
$cLabel
]),
'assignee_id' => GitlabUsersEnum::olivier,
]);
} catch (\Exception $e) {
\report($e);
return back()->with('error', \trans("Une erreur est survenue"));
} catch (\Throwable $e) {
\report($e);
return back()->with('error', \trans("Une erreur est survenue"));
} //end try
$ticket->gitlab_issue = $issue['web_url'];
$ticket->save();
return back()->with(
'success',
trans("L'issue a été créée sur Gitlab.")
);
}
/**
* Format files for Gitlab issues
*
* @param \App\Models\Ticket $ticket
* @param array $uploadedFile
* @param integer $fileNb
* @return string
*/
private function formatGitlabFiles(Ticket $ticket, array $uploadedFile, int $fileNb)
{
if (Str::endsWith($ticket->file, ['jpg', 'png', 'jpeg'])) {
$file = \sprintf(
"![Fichier joint %d](%s)",
$fileNb,
$uploadedFile['url']
);
} else {
$file = \sprintf(
'<a href="%s" target="_blank">%s</a>',
$uploadedFile['url'],
__('Lien vers le fichier'),
);
}
return \sprintf("<br><br> Fichier %d : <br>%s<br>", $fileNb, $file);
}
}

View file

@ -120,10 +120,7 @@ class UserController extends Controller
{
return DB::transaction(function () use ($request, $user) {
$user->fill($request->safe()->except(['sites']));
$user->sites()->detach();
if (collect($request->validated('sites'))->pluck('id')) {
$user->sites()->attach(collect($request->validated('sites'))->pluck('id'));
}
$user->sites()->sync(collect($request->validated('sites'))->pluck('id'));
if ($user->saveOrFail()) {
return redirect()
->route('bo.users.edit', $user)

View file

@ -2,7 +2,7 @@
namespace App\Http\Requests\Bo\Invoices;
use App\Enums\Invoice\TaskTypeEnum;
use App\Enums\Invoices\TaskTypeEnum;
use App\Models\Invoice;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
@ -90,7 +90,8 @@ class StoreInvoiceRequest extends FormRequest
'invoiced_to_id' => trans('Entité à facturer'),
'signed_date' => trans('Date de signature du Devis'),
'sent_client_date' => trans('Date d\'envoi du Devis au client'),
'sent_accounting_date' => trans('Date d\'envoi du Devis à la compta'),
'sent_accounting_date' => trans('Date d\'envoi du Devis à la comptabilité'),
'status' => trans('État du devis'),
];
}
}

View file

@ -2,6 +2,9 @@
namespace App\Http\Requests\Bo\Invoices;
use App\Enums\Invoices\InvoiceStatusEnum;
use Illuminate\Validation\Rules\Enum;
class UpdateInvoiceRequest extends StoreInvoiceRequest
{
/**
@ -12,14 +15,34 @@ class UpdateInvoiceRequest extends StoreInvoiceRequest
public function rules()
{
$rules = [];
if (!$this->route('invoice')->signed_date) {
$rules = array_merge($rules, parent::rules());
}
$rules = array_merge($rules, [
'signed_date' => 'sometimes|nullable|date',
'sent_client_date' => 'sometimes|nullable|date',
'sent_accounting_date' => 'sometimes|nullable|date',
]);
$model = $this->route('invoice');
if ($model->status->value === InvoiceStatusEnum::opened->value) {
if (!$model->sent_client_date) {
$rules = array_merge($rules, parent::rules());
}
$rules = array_merge($rules, [
'sent_client_date' => 'required_with:signed_date,sent_accounting_date|sometimes|nullable|date',
'signed_date' => 'required_with:sent_accounting_date|sometimes|nullable|date',
'sent_accounting_date' => 'sometimes|nullable|date',
'status' => [
'required',
new Enum(InvoiceStatusEnum::class),
//phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) use ($model) {
if (
(intval($value) === InvoiceStatusEnum::archived->value) &&
(is_null($model->sent_client_date) ||
is_null($model->signed_date) ||
is_null($model->sent_accounting_date))
) {
$fail(__('Le status :status doit être assigné lorsque les 3 dates sont saisies', [
'status' => InvoiceStatusEnum::archived->label()
]));
}
},
],
]);
} //end if
return $rules;
}
}

View file

@ -27,7 +27,8 @@ class StoreSiteRequest extends FormRequest
return [
'name' => 'required|string|min:3|max:255',
'users' => 'nullable|array',
'users.*.id' => 'required|exists:users,id'
'users.*.id' => 'required|exists:users,id',
'gitlab_project_id' => 'nullable|unique:sites,gitlab_project_id'
];
}
@ -42,6 +43,7 @@ class StoreSiteRequest extends FormRequest
'name' => trans('Nom du site'),
'users' => trans('Utilisateurs associés au site'),
'users.*.id' => trans('Élément parmis les Utilisateurs associés au site'),
'gitlab_project_id' => trans('ID du dépôt Gitlab')
];
}
}

View file

@ -4,4 +4,15 @@ namespace App\Http\Requests\Bo\Sites;
class UpdateSiteRequest extends StoreSiteRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return array_merge(parent::rules(), [
'gitlab_project_id' => 'nullable|unique:sites,gitlab_project_id,' . $this->route('site')->id . ',id',
]);
}
}

View file

@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
/**
* @property string $name
@ -21,6 +22,15 @@ class Company extends Model
'name'
];
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = [
'value'
];
/**
* HasMany Invoices
*
@ -30,4 +40,16 @@ class Company extends Model
{
return $this->hasMany(Invoice::class);
}
/**
* Return value attribute for belongsto search component
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function value(): Attribute
{
return new Attribute(
get: fn () => $this->id,
);
}
}

View file

@ -2,7 +2,8 @@
namespace App\Models;
use App\Enums\Invoice\TaskTypeEnum;
use App\Enums\Invoices\TaskTypeEnum;
use App\Enums\Invoices\InvoiceStatusEnum;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
@ -26,6 +27,7 @@ use Mccarlosen\LaravelMpdf\LaravelMpdf;
* @property Site $site_id
* @property Company $company_id
* @property Company $invoiced_to_id
* @property InvoiceStatusEnum $status
*/
class Invoice extends Model
{
@ -51,7 +53,8 @@ class Invoice extends Model
'sent_accounting_date',
'site_id',
'company_id',
'invoiced_to_id'
'invoiced_to_id',
'status',
];
/**
@ -64,6 +67,7 @@ class Invoice extends Model
'signed_date' => 'date',
'sent_client_date' => 'date',
'sent_accounting_date' => 'date',
'status' => InvoiceStatusEnum::class,
];
/**
@ -92,6 +96,7 @@ class Invoice extends Model
->reduce(function ($previous, array $task) {
return \floatval($task['amount']) + \floatval($previous);
});
$model->status = InvoiceStatusEnum::opened;
});
static::updating(function (self $model) {
$model->total_days = collect($model->tasks)
@ -215,4 +220,36 @@ class Invoice extends Model
);
return $pdf;
}
/**
* Get the invoice status array depending to the dates entered.
*
* @param \App\Models\Invoice $invoice
* @return array
*/
public static function getInvoiceStatusArray(Invoice $invoice): array
{
$invoiceStatusArray = [];
if ($invoice->signed_date && $invoice->sent_client_date && $invoice->sent_accounting_date) {
return array_merge($invoiceStatusArray, [
InvoiceStatusEnum::toArray()['opened'],
InvoiceStatusEnum::toArray()['archived'],
]);
}
if ($invoice->signed_date) {
return array_merge($invoiceStatusArray, [
InvoiceStatusEnum::toArray()['opened']
]);
}
if ($invoice->sent_client_date) {
return array_merge($invoiceStatusArray, [
InvoiceStatusEnum::toArray()['opened'],
InvoiceStatusEnum::toArray()['refused'],
]);
}
return array_merge($invoiceStatusArray, [
InvoiceStatusEnum::toArray()['opened'],
InvoiceStatusEnum::toArray()['canceled'],
]);
}
}

View file

@ -51,7 +51,9 @@ class Message extends Model
$q->withTrashed();
}]);
/** @var \App\Models\Ticket $ticket */
$ticket = $message->ticket;
$ticket = $message->ticket;
$ticket->last_activity = $message->updated_at;
$ticket->saveQuietly();
if ($message->user) {
$ticket->load(['user' => function ($q) {
$q->withTrashed();

View file

@ -4,10 +4,12 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
/**
* @property string $name
* @property Brand $brand_id
* @property string $name
* @property integer $gitlab_project_id
* @property Brand $brand_id
*/
class Site extends Model
{
@ -20,6 +22,16 @@ class Site extends Model
*/
protected $fillable = [
'name',
'gitlab_project_id'
];
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = [
'value'
];
// * RELATIONSHIPS
@ -64,4 +76,16 @@ class Site extends Model
{
return $this->belongsTo(Project::class);
}
/**
* Return value attribute for belongsto search component
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function value(): Attribute
{
return new Attribute(
get: fn () => $this->id,
);
}
}

View file

@ -33,7 +33,8 @@ class Ticket extends Model
'type',
'status',
'text',
'is_internal'
'is_internal',
'last_activity',
];
/**
@ -44,7 +45,8 @@ class Ticket extends Model
protected $casts = [
'is_internal' => 'bool',
'type' => TicketTypeEnum::class,
'status' => TicketStatusEnum::class
'status' => TicketStatusEnum::class,
'last_activity' => 'timestamp',
];
/**

View file

@ -3,6 +3,7 @@
namespace App\Policies;
use App\Enums\Tickets\TicketStatusEnum;
use App\Enums\Tickets\TicketTypeEnum;
use App\Enums\Users\RoleEnum;
use App\Models\Ticket;
use App\Models\User;
@ -260,4 +261,19 @@ class TicketPolicy
return $user->role->equals(RoleEnum::developper)
&& $ticket->estimation()->exists();
}
/**
* Determine if the user can create a gitlab issue from a given ticket.
*
* @param \App\Models\User $user
* @param \App\Models\Ticket $ticket
* @return boolean
*/
public function createGitlabIssue(User $user, Ticket $ticket)
{
return $user->role->equals(RoleEnum::developper)
&& ($ticket->type->equals(TicketTypeEnum::bug) || $ticket->type->equals(TicketTypeEnum::evolution))
&& !\is_null($ticket->site->gitlab_project_id)
&& \is_null($ticket->gitlab_issue);
}
}

View file

@ -13,6 +13,7 @@
"carlos-meneses/laravel-mpdf": "^2.1",
"diglactic/laravel-breadcrumbs": "^8.0",
"doctrine/dbal": "^3.1",
"graham-campbell/gitlab": "^7.0",
"guzzlehttp/guzzle": "^7.2",
"laravel-lang/lang": "~8.0",
"laravel-validation-rules/colour": "^1.6",
@ -20,7 +21,6 @@
"laravel/tinker": "^2.8",
"laravel/ui": "^4.2",
"ldc/web-admin-secure-laravel": "^2.0",
"ldc/web-status-checker-laravel": "^1.0",
"mews/purifier": "^3.4",
"sentry/sentry-laravel": "*",
"uwil/k8s-laravel": "^1.2"

777
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8b8d80affdf1ee455767d5e675bf5fe8",
"content-hash": "4f5fe189b89c5ccc99fc4c7a913a64a6",
"packages": [
{
"name": "brick/math",
@ -1251,6 +1251,228 @@
],
"time": "2022-02-20T15:07:15+00:00"
},
{
"name": "graham-campbell/bounded-cache",
"version": "v2.1.1",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Bounded-Cache.git",
"reference": "2840f46f38185e610e62af3662dac75ca1877a84"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Bounded-Cache/zipball/2840f46f38185e610e62af3662dac75ca1877a84",
"reference": "2840f46f38185e610e62af3662dac75ca1877a84",
"shasum": ""
},
"require": {
"php": "^8.0.2",
"psr/simple-cache": "^2.0 || ^3.0"
},
"require-dev": {
"graham-campbell/analyzer": "^4.0",
"graham-campbell/testbench-core": "^4.0",
"mockery/mockery": "^1.5.1",
"phpunit/phpunit": "^9.6.3 || ^10.0.12"
},
"type": "library",
"autoload": {
"psr-4": {
"GrahamCampbell\\BoundedCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "A Bounded TTL PSR-16 Cache Implementation",
"keywords": [
"Bounded Cache",
"Bounded-Cache",
"Graham Campbell",
"GrahamCampbell",
"bounded",
"cache",
"psr16"
],
"support": {
"issues": "https://github.com/GrahamCampbell/Bounded-Cache/issues",
"source": "https://github.com/GrahamCampbell/Bounded-Cache/tree/v2.1.1"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/bounded-cache",
"type": "tidelift"
}
],
"time": "2023-02-25T23:00:30+00:00"
},
{
"name": "graham-campbell/gitlab",
"version": "v7.0.0",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Laravel-GitLab.git",
"reference": "fb037dedadacb16e4ab75b8bab58b52b703a4e33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Laravel-GitLab/zipball/fb037dedadacb16e4ab75b8bab58b52b703a4e33",
"reference": "fb037dedadacb16e4ab75b8bab58b52b703a4e33",
"shasum": ""
},
"require": {
"graham-campbell/bounded-cache": "^1.2 || ^2.0",
"graham-campbell/manager": "^5.0",
"guzzlehttp/guzzle": "^7.5",
"guzzlehttp/psr7": "^2.4",
"illuminate/contracts": "^8.75 || ^9.0 || ^10.0",
"illuminate/support": "^8.75 || ^9.0 || ^10.0",
"m4tthumphrey/php-gitlab-api": "11.9.*",
"php": "^7.4.15 || ^8.0.2",
"symfony/cache": "^5.4 || ^6.0"
},
"require-dev": {
"graham-campbell/analyzer": "^4.0",
"graham-campbell/testbench": "^6.0",
"mockery/mockery": "^1.5.1",
"phpunit/phpunit": "^9.6.3 || ^10.0.12"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"GrahamCampbell\\GitLab\\GitLabServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"GrahamCampbell\\GitLab\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "GitLab Is A GitLab Bridge For Laravel",
"keywords": [
"Bridge",
"Graham Campbell",
"GrahamCampbell",
"Laravel GitLab",
"Laravel-GitLab",
"PHP GitLab API",
"framework",
"gitlab",
"gitlab bridge",
"laravel",
"php-gitlab-api"
],
"support": {
"issues": "https://github.com/GrahamCampbell/Laravel-GitLab/issues",
"source": "https://github.com/GrahamCampbell/Laravel-GitLab/tree/v7.0.0"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/gitlab",
"type": "tidelift"
}
],
"time": "2023-03-07T11:41:34+00:00"
},
{
"name": "graham-campbell/manager",
"version": "v5.0.0",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Laravel-Manager.git",
"reference": "f1cf95a0bb11ba086387b8049e0e40b82d01588d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Laravel-Manager/zipball/f1cf95a0bb11ba086387b8049e0e40b82d01588d",
"reference": "f1cf95a0bb11ba086387b8049e0e40b82d01588d",
"shasum": ""
},
"require": {
"illuminate/contracts": "^8.75 || ^9.0 || ^10.0",
"illuminate/support": "^8.75 || ^9.0 || ^10.0",
"php": "^7.4.15 || ^8.0.2"
},
"require-dev": {
"graham-campbell/analyzer": "^4.0",
"graham-campbell/testbench-core": "^4.0",
"mockery/mockery": "^1.5.1",
"phpunit/phpunit": "^9.6.3 || ^10.0.12"
},
"type": "library",
"autoload": {
"psr-4": {
"GrahamCampbell\\Manager\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "Manager Provides Some Manager Functionality For Laravel",
"keywords": [
"Graham Campbell",
"GrahamCampbell",
"Laravel Manager",
"Laravel-Manager",
"connector",
"framework",
"interface",
"laravel",
"manager"
],
"support": {
"issues": "https://github.com/GrahamCampbell/Laravel-Manager/issues",
"source": "https://github.com/GrahamCampbell/Laravel-Manager/tree/v5.0.0"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/manager",
"type": "tidelift"
}
],
"time": "2023-02-25T23:53:34+00:00"
},
{
"name": "graham-campbell/result-type",
"version": "v1.1.1",
@ -2400,113 +2622,6 @@
],
"description": "Restrict access to LDC Network"
},
{
"name": "ldc/web-status-checker",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://lab.s-web.fr/ldc-web/projets/ldc-web-status-checker.git",
"reference": "7ce62449f3eb052de2d7bfa2e32d37d4524d60b0"
},
"dist": {
"type": "zip",
"url": "https://lab.s-web.fr/api/v4/projects/21/packages/composer/archives/ldc/web-status-checker.zip?sha=7ce62449f3eb052de2d7bfa2e32d37d4524d60b0",
"reference": "7ce62449f3eb052de2d7bfa2e32d37d4524d60b0",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": "~7.3|7.4|~8"
},
"require-dev": {
"slevomat/coding-standard": "^7.0",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"autoload": {
"psr-4": {
"LDC\\WebStatusChecker\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"LDC\\WebStatusChecker\\Tests\\": "tests"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "Jérémy Munsch",
"email": "jeremy.munsch@ldc.fr",
"homepage": "https://jeremydev.ovh"
}
],
"description": "Status checker: report status to ldc-web"
},
{
"name": "ldc/web-status-checker-laravel",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://lab.s-web.fr/ldc-web/projets/ldc-web-status-checker-laravel.git",
"reference": "3918891846f1e8440ba54b23dc47422f005070b9"
},
"dist": {
"type": "zip",
"url": "https://lab.s-web.fr/api/v4/projects/22/packages/composer/archives/ldc/web-status-checker-laravel.zip?sha=3918891846f1e8440ba54b23dc47422f005070b9",
"reference": "3918891846f1e8440ba54b23dc47422f005070b9",
"shasum": ""
},
"require": {
"illuminate/support": "~8|~9|~10",
"laravel/framework": "~8|~9|~10",
"ldc/web-status-checker": "^1.1.0",
"php": "~7.3|7.4|~8"
},
"require-dev": {
"slevomat/coding-standard": "^7.0",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"LDC\\WebStatusCheckerLaravel\\StatusCheckerServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"LDC\\WebStatusCheckerLaravel\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"LDC\\WebStatusCheckerLaravel\\Tests\\": "tests"
}
},
"scripts": {
"test": [
"vendor/bin/phpunit"
],
"post-autoload-dump": [
"@php ./vendor/bin/testbench package:discover --ansi"
]
},
"license": [
"MIT"
],
"authors": [
{
"name": "Jérémy Munsch",
"email": "jeremy.munsch@ldc.fr",
"homepage": "https://jeremydev.ovh"
}
],
"description": "Status checker: report status to ldc-web"
},
{
"name": "league/commonmark",
"version": "2.4.0",
@ -2899,6 +3014,96 @@
],
"time": "2022-04-17T13:12:02+00:00"
},
{
"name": "m4tthumphrey/php-gitlab-api",
"version": "11.9.1",
"source": {
"type": "git",
"url": "https://github.com/GitLabPHP/Client.git",
"reference": "148ed10609f92e03afbf756e734ee4b76be36832"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GitLabPHP/Client/zipball/148ed10609f92e03afbf756e734ee4b76be36832",
"reference": "148ed10609f92e03afbf756e734ee4b76be36832",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-xml": "*",
"php": "^7.4.15 || ^8.0.2",
"php-http/cache-plugin": "^1.7.5",
"php-http/client-common": "^2.6",
"php-http/discovery": "^1.15",
"php-http/httplug": "^2.3",
"php-http/multipart-stream-builder": "^1.2",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"psr/http-client-implementation": "^1.0",
"psr/http-factory-implementation": "^1.0",
"psr/http-message": "^1.0",
"symfony/options-resolver": "^4.4 || ^5.0 || ^6.0",
"symfony/polyfill-php80": "^1.26"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"guzzlehttp/guzzle": "^7.5",
"http-interop/http-factory-guzzle": "^1.0"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"psr-4": {
"Gitlab\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Bourigault",
"email": "bourigaultfabien@gmail.com",
"homepage": "https://github.com/fbourigault"
},
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Matt Humphrey",
"email": "matth@windsor-telecom.co.uk",
"homepage": "https://github.com/m4tthumphrey"
},
{
"name": "Miguel Piedrafita",
"email": "github@miguelpiedrafita.com",
"homepage": "https://github.com/m1guelpf"
}
],
"description": "GitLab API v4 client for PHP",
"keywords": [
"api",
"gitlab"
],
"support": {
"issues": "https://github.com/GitLabPHP/Client/issues",
"source": "https://github.com/GitLabPHP/Client/tree/11.9.1"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
}
],
"time": "2023-04-30T19:02:00+00:00"
},
{
"name": "mews/purifier",
"version": "3.4.0",
@ -3779,6 +3984,60 @@
},
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "php-http/cache-plugin",
"version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/cache-plugin.git",
"reference": "6bf9fbf66193f61d90c2381b75eb1fa0202fd314"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/cache-plugin/zipball/6bf9fbf66193f61d90c2381b75eb1fa0202fd314",
"reference": "6bf9fbf66193f61d90c2381b75eb1fa0202fd314",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"php-http/client-common": "^1.9 || ^2.0",
"php-http/message-factory": "^1.0",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"symfony/options-resolver": "^2.6 || ^3.0 || ^4.0 || ^5.0 || ^6.0"
},
"require-dev": {
"phpspec/phpspec": "^5.1 || ^6.0 || ^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Http\\Client\\Common\\Plugin\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "PSR-6 Cache plugin for HTTPlug",
"homepage": "http://httplug.io",
"keywords": [
"cache",
"http",
"httplug",
"plugin"
],
"support": {
"issues": "https://github.com/php-http/cache-plugin/issues",
"source": "https://github.com/php-http/cache-plugin/tree/1.8.0"
},
"time": "2023-04-28T10:56:55+00:00"
},
{
"name": "php-http/client-common",
"version": "2.7.0",
@ -4107,6 +4366,62 @@
"abandoned": "psr/http-factory",
"time": "2023-04-14T14:16:17+00:00"
},
{
"name": "php-http/multipart-stream-builder",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/multipart-stream-builder.git",
"reference": "f5938fd135d9fa442cc297dc98481805acfe2b6a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/f5938fd135d9fa442cc297dc98481805acfe2b6a",
"reference": "f5938fd135d9fa442cc297dc98481805acfe2b6a",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"php-http/discovery": "^1.15",
"psr/http-factory-implementation": "^1.0"
},
"require-dev": {
"nyholm/psr7": "^1.0",
"php-http/message": "^1.5",
"php-http/message-factory": "^1.0.2",
"phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Http\\Message\\MultipartStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
}
],
"description": "A builder class that help you create a multipart stream",
"homepage": "http://php-http.org",
"keywords": [
"factory",
"http",
"message",
"multipart stream",
"stream"
],
"support": {
"issues": "https://github.com/php-http/multipart-stream-builder/issues",
"source": "https://github.com/php-http/multipart-stream-builder/tree/1.3.0"
},
"time": "2023-04-28T14:10:22+00:00"
},
{
"name": "php-http/promise",
"version": "1.1.0",
@ -5387,6 +5702,178 @@
],
"time": "2023-02-09T10:38:43+00:00"
},
{
"name": "symfony/cache",
"version": "v6.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
"reference": "d176b97600860df13e851538c2df2ad88e9e5ca9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/cache/zipball/d176b97600860df13e851538c2df2ad88e9e5ca9",
"reference": "d176b97600860df13e851538c2df2ad88e9e5ca9",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/cache": "^2.0|^3.0",
"psr/log": "^1.1|^2|^3",
"symfony/cache-contracts": "^2.5|^3",
"symfony/service-contracts": "^2.5|^3",
"symfony/var-exporter": "^6.2.10"
},
"conflict": {
"doctrine/dbal": "<2.13.1",
"symfony/dependency-injection": "<5.4",
"symfony/http-kernel": "<5.4",
"symfony/var-dumper": "<5.4"
},
"provide": {
"psr/cache-implementation": "2.0|3.0",
"psr/simple-cache-implementation": "1.0|2.0|3.0",
"symfony/cache-implementation": "1.1|2.0|3.0"
},
"require-dev": {
"cache/integration-tests": "dev-master",
"doctrine/dbal": "^2.13.1|^3.0",
"predis/predis": "^1.1|^2.0",
"psr/simple-cache": "^1.0|^2.0|^3.0",
"symfony/config": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/filesystem": "^5.4|^6.0",
"symfony/http-kernel": "^5.4|^6.0",
"symfony/messenger": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Cache\\": ""
},
"classmap": [
"Traits/ValueWrapper.php"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides extended PSR-6, PSR-16 (and tags) implementations",
"homepage": "https://symfony.com",
"keywords": [
"caching",
"psr6"
],
"support": {
"source": "https://github.com/symfony/cache/tree/v6.3.2"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-07-27T16:19:14+00:00"
},
{
"name": "symfony/cache-contracts",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache-contracts.git",
"reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ad945640ccc0ae6e208bcea7d7de4b39b569896b",
"reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/cache": "^3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Cache\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to caching",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/cache-contracts/tree/v3.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-05-23T14:45:45+00:00"
},
{
"name": "symfony/console",
"version": "v6.3.0",
@ -7955,6 +8442,80 @@
],
"time": "2023-06-21T12:08:28+00:00"
},
{
"name": "symfony/var-exporter",
"version": "v6.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
"reference": "3400949782c0cb5b3e73aa64cfd71dde000beccc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/3400949782c0cb5b3e73aa64cfd71dde000beccc",
"reference": "3400949782c0cb5b3e73aa64cfd71dde000beccc",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"symfony/var-dumper": "^5.4|^6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\VarExporter\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Allows exporting any serializable PHP data structure to plain PHP code",
"homepage": "https://symfony.com",
"keywords": [
"clone",
"construct",
"export",
"hydrate",
"instantiate",
"lazy-loading",
"proxy",
"serialize"
],
"support": {
"source": "https://github.com/symfony/var-exporter/tree/v6.3.2"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-07-26T17:39:03+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "2.2.6",

74
config/gitlab.php Normal file
View file

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/*
* This file is part of Laravel GitLab.
*
* (c) Graham Campbell <hello@gjcampbell.co.uk>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
/**
*--------------------------------------------------------------------------
* Default Connection Name
*--------------------------------------------------------------------------
*
* Here you may specify which of the connections below you wish to use as
* your default connection for all work. Of course, you may use many
* connections at once using the manager class.
*
*/
'default' => 'main',
/**
*--------------------------------------------------------------------------
* GitLab Connections
*--------------------------------------------------------------------------
*
* Here are each of the connections setup for your application. Example
* configuration has been included, but you may add as many connections as
* you would like. Note that the 4 supported authentication methods are:
* "none", "oauth", "job_token", and "token".
*
*/
'connections' => [
'main' => [
'token' => env('GITLAB_TOKEN'),
'method' => 'token',
'url' => env('GITLAB_URL'),
],
],
/**
*--------------------------------------------------------------------------
* HTTP Cache
*--------------------------------------------------------------------------
*
* Here are each of the cache configurations setup for your application.
* Only the "illuminate" driver is provided out of the box. Example
* configuration has been included.
*
*/
'cache' => [
'main' => [
'driver' => 'illuminate',
'connector' => null,
],
'bar' => [
'driver' => 'illuminate',
'connector' => 'redis',
],
],
];

View file

@ -2,7 +2,8 @@
namespace Database\Factories;
use App\Enums\Invoice\TaskTypeEnum;
use App\Enums\Invoices\TaskTypeEnum;
use App\Enums\Invoices\InvoiceStatusEnum;
use App\Models\Invoice;
use App\Models\Package;
use Illuminate\Database\Eloquent\Factories\Factory;
@ -16,18 +17,58 @@ class InvoiceFactory extends Factory
*/
protected $model = Invoice::class;
/**
* A JSON object, each line has to be contructed using these
* fields description, days, type Invoice\TaskType, amount.
*
* @var array
*/
protected $tasks = [];
/**
* The total calculated using all tasks.
*
* @var float
*/
protected $total = 0;
/**
* The total days calculated using all tasks.
*
* @var float
*/
protected $totalDays = 0;
/**
* The date of the signing.
*
* @var boolean
*/
protected $signedDateBoolean = 0;
/**
* The date of the shipping to the client.
*
* @var boolean
*/
protected $sentClientDateBoolean = 0;
/**
* The date of the shipping to the accounting.
*
* @var boolean
*/
protected $sentAccountingDateBoolean = 0;
/**
* Define the model's default state.
*
* @return array
* @return void
*/
public function definition()
private function customData(): void
{
\mt_srand();
$lines = \mt_rand(2, 9);
$tasks = [];
$total = 0;
$totalDays = 0;
$lines = \mt_rand(2, 9);
for ($i = 0; $i < $lines; $i++) {
$days = 0.125 * $this->faker->numberBetween(1, 4);
$type = \collect(TaskTypeEnum::cases())->random();
@ -47,37 +88,54 @@ class InvoiceFactory extends Factory
$amount = $days * $this->faker->randomFloat(null, 150, 650);
break;
}
$totalDays += $days;
$total += $amount;
/** @var \App\Enums\Invoice\TaskTypeEnum */
$taskType = \collect(TaskTypeEnum::cases())->random();
$tasks[] = \array_merge(Invoice::getTaskStructure(), [
$this->totalDays += $days;
$this->total += $amount;
/** @var \App\Enums\Invoices\TaskTypeEnum */
$this->tasks[] = \array_merge(Invoice::getTaskStructure(), [
'desc' => $this->faker->words(9, true),
'days' => $days,
'type' => $taskType->value,
'type' => \collect(TaskTypeEnum::cases())->random()->value,
'amount' => $amount,
'package' => 1
]);
/** Dates */
$this->signedDateBoolean = $this->faker->boolean(25);
$this->sentClientDateBoolean = $this->faker->boolean(25);
$this->sentAccountingDateBoolean = $this->faker->boolean(25);
} //end for
}
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
$this->customData();
return [
'firstname' => $this->faker->firstName,
'lastname' => $this->faker->lastName,
'object' => $this->faker->words(3, true),
// * constructed object
'tasks' => $tasks,
'tasks' => $this->tasks,
// * constructed object result
'total' => $total,
'total' => $this->total,
// * constructed object result
'total_days' => $totalDays,
'total_days' => $this->totalDays,
'comment' => $this->faker->text(\rand(20, 40)),
'show_days' => $this->faker->boolean(75),
'signed_date' => $this->faker->boolean(25) ?
'signed_date' =>
$this->signedDateBoolean ?
$this->faker->dateTimeBetween('-1 year', '+1 year') : null,
'sent_client_date' => $this->faker->boolean(25) ?
'sent_client_date' =>
$this->signedDateBoolean && $this->sentClientDateBoolean ?
$this->faker->dateTimeBetween('-1 year', '+1 year') : null,
'sent_accounting_date' => $this->faker->boolean(25) ?
'sent_accounting_date' =>
$this->signedDateBoolean && $this->sentClientDateBoolean && $this->sentAccountingDateBoolean ?
$this->faker->dateTimeBetween('-1 year', '+1 year') : null,
'status' => $this->signedDateBoolean && $this->sentClientDateBoolean && $this->sentAccountingDateBoolean ?
InvoiceStatusEnum::archived : InvoiceStatusEnum::opened,
];
}
}

View file

@ -34,7 +34,7 @@ class CreateInvoicesTable extends Migration
$table->text('tasks')
->comment(
// phpcs:ignore Generic.Files.LineLength.TooLong
'A JSON object, each line has to be contructed using these fields description, days, type Invoice\TaskTypeEnum, amount'
'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');

View file

@ -0,0 +1,37 @@
<?php
use App\Enums\Invoices\InvoiceStatusEnum;
use App\Models\Invoice;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->unsignedTinyInteger('status')
->default(InvoiceStatusEnum::opened->value)
->comment('Status of the invoice.')
->after('invoiced_to_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->dropColumn('status');
});
}
};

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('sites', function (Blueprint $table) {
$table->unsignedInteger('gitlab_project_id')
->unique()
->nullable()
->comment("ID du dépôt Gitlab")
->after('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('sites', function (Blueprint $table) {
$table->dropColumn('gitlab_project_id');
});
}
};

View file

@ -0,0 +1,43 @@
<?php
use App\Models\Ticket;
use Carbon\Carbon;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->timestamp('last_activity')
->default(Carbon::now())
->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();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->dropColumn('last_activity');
});
}
};

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->string('gitlab_issue')
->nullable()
->comment("URL de l'issue Gitlab")
->after('is_internal');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->dropColumn('gitlab_issue');
});
}
};

View file

@ -155,6 +155,7 @@ return [
'excerpt' => 'extrait',
'firstname' => 'prénom',
'gender' => 'genre',
'gitlab_project_id' => 'id du dépôt Gitlab',
'hour' => 'heure',
'invoiced_to_id' => 'Entité à facturer',
'label' => 'label',

View file

@ -28,15 +28,6 @@ export default defineComponent({
throw new Error("this component requires json");
}
},
mounted() {
const json = String(this.$attrs.json ?? "{}"),
data = JSON.parse(json);
this.name = data.name;
this.internalText = data.text ? String(data.text) : "";
this.internalId = data.id ? data.id : "";
this.internalRequired = Boolean(data.required);
this.makeSuperBuild();
},
data(): {
name: string;
internalText: string;
@ -57,6 +48,15 @@ export default defineComponent({
chars: 0,
};
},
mounted() {
const json = String(this.$attrs.json ?? "{}"),
data = JSON.parse(json);
this.name = data.name;
this.internalText = data.text ? String(data.text) : "";
this.internalId = data.id ? data.id : "";
this.internalRequired = Boolean(data.required);
this.makeSuperBuild();
},
watch: {
dbtext() {
if (this.internalText != this.dbtext) {

View file

@ -8,7 +8,9 @@ import {
faArrowRightLong,
faArrowUp,
faBan,
faBox,
faBoxArchive,
faBoxOpen,
faChevronLeft,
faChevronRight,
faClipboard,
@ -43,7 +45,9 @@ import {
config.autoReplaceSvg = false;
library.add(faTags);
library.add(faSquarePlus);
library.add(faBox);
library.add(faBoxArchive);
library.add(faBoxOpen);
library.add(faEraser);
library.add(faSignHanging);
library.add(faDiagramProject);

View file

@ -31,7 +31,7 @@
@foreach (\App\Enums\ItemsPerPaginationEnum::toArray() as $itemsPerPaginationEnum)
<li>
<a class="dropdown-item text-center user-select-none @if($pagination === $itemsPerPaginationEnum->value) active @endif"
href="{{ request()->fullUrlWithQuery(['pagination' => $itemsPerPaginationEnum->value]) . $queryString }}"
href="{{ request()->fullUrlWithQuery(['page' => 1, 'pagination' => $itemsPerPaginationEnum->value]) . $queryString }}"
role="button">
{{ $itemsPerPaginationEnum->value }}
</a>

View file

@ -7,7 +7,7 @@
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-3">
<div class="d-flex flex-row align-items-center">
<a href="{{ route('bo.invoices.index') }}"
<a href="{{ route('bo.invoices.index', ['status' => \App\Enums\Invoices\InvoiceStatusEnum::opened]) }}"
class="btn btn-primary text-decoration-none m-0 me-2"
data-bs-toggle="tooltip"
data-bs-placement="top"

View file

@ -7,7 +7,7 @@
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-3">
<div class="d-flex flex-row align-items-start">
<a href="{{ route('bo.invoices.index') }}"
<a href="{{ route('bo.invoices.index', ['status' => \App\Enums\Invoices\InvoiceStatusEnum::opened]) }}"
class="btn btn-primary text-decoration-none m-0 me-2"
data-bs-toggle="tooltip"
data-bs-placement="top"
@ -25,6 +25,7 @@
title="{{ __('crud.helpers.view_model', ['model' => __('models.classes.invoices')]) }}">
<i class="fa-solid fa-eye"></i>
</a>
@if($invoiceModel->status->value === \App\Enums\Invoices\InvoiceStatusEnum::opened->value)
<button id="formSubmitClone"
type="submit"
class="btn btn-primary"
@ -33,9 +34,11 @@
title="{{ __('crud.helpers.save_model', ['model' => __('models.classes.invoices')]) }}">
{{ __('crud.actions.save') }}
</button>
@endif
</div>
</div>
</div>
@if($invoiceModel->status->value === \App\Enums\Invoices\InvoiceStatusEnum::opened->value)
<form action="{{ route('bo.invoices.update', $invoiceModel->id) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
@ -53,4 +56,7 @@
</div>
</div>
</form>
@else
@include('bo.pages.invoices.form-inputs')
@endif
@endsection

View file

@ -1,41 +1,30 @@
<div class="row">
{{-- DATES/STATUS --}}
@if (request()->routeIs('bo.invoices.edit'))
@if($invoiceModel->status !== \App\Enums\Invoices\InvoiceStatusEnum::opened || $invoiceModel->signed_date)
<div class="col-12">
<div class="alert alert-secondary text-white" role="alert">
<strong>
@if($invoiceModel->status !== \App\Enums\Invoices\InvoiceStatusEnum::opened)
{{ __('Impossible de mettre à jour les informations d\'un devis :status.', ['status' => $invoiceModel->status->label()]) }}
@elseif($invoiceModel->signed_date)
{{ __('Impossible de mettre à jour les informations d\'un devis déjà signé, seule la section "État" est prise en compte') }}
@endif
</strong>
</div>
</div>
@endif
{{-- DATES STATUS --}}
<div class="col-12 mb-3">
<fieldset class="row border w-100 h-100 bg-white mx-auto p-3">
<legend class="p-0">{{ __('Dates') }}</legend>
<legend class="p-0">{{ __('État') }}</legend>
<div class="row w-100 mx-auto p-0">
@if($invoiceModel->signed_date)
<div class="col-12 p-0 pe-md-3">
<div class="alert alert-secondary text-white" role="alert">
<strong>{{ __('Impossible de mettre à jour les informations d\'un devis déjà signé, seules les dates sont prises en compte !') }}</strong>
</div>
</div>
@endif
{{-- SIGNED_DATE --}}
<div class="col-12 col-sm-6 col-md-4 p-0 pe-md-3">
@if(!$invoiceModel->signed_date)
@include('bo.partials.inputs.timestamp-datetime', [
'id' => 'invoiceSigneddate',
'label' => __('Date d\'envoi du :model', [
'model' => __('models.classes.invoices'),
]),
'name' => 'signed_date',
'defaultValue' => false,
'required' => true,
'model' => $invoiceModel ?? null,
'helper' => __('Doît être une date réelle et existante.'),
])
@else
<p class="fw-bold text-center m-0">{{ __('Envoyé le :date', ['date' => $invoiceModel->signed_date]) }}</p>
@endif
</div>
{{-- SENT_CLIENT_DATE --}}
<div class="col-12 col-sm-6 col-md-4 p-0 pe-md-3">
@if(!$invoiceModel->sent_client_date)
@include('bo.partials.inputs.timestamp-datetime', [
@if($invoiceModel->status === \App\Enums\Invoices\InvoiceStatusEnum::opened AND !$invoiceModel->sent_client_date )
@include('bo.partials.inputs.date', [
'id' => 'invoiceSentclientdate',
'label' => __('Date de signature du :model (client)', [
'label' => __('Date d\'envoi du :model', [
'model' => __('models.classes.invoices'),
]),
'name' => 'sent_client_date',
@ -44,16 +33,38 @@
'model' => $invoiceModel ?? null,
'helper' => __('Doît être une date réelle et existante.'),
])
@else
<p class="fw-bold text-center m-0">{{ __('Signé le :date (client)', ['date' => $invoiceModel->sent_client_date]) }}</p>
@elseif ($invoiceModel->sent_client_date)
<p class="fw-bold text-center m-0">
{{ __('Envoyé le :date', ['date' => \Carbon\Carbon::parse($invoiceModel->sent_client_date)->format('d/m/Y')]) ?? "xxx" }}
</p>
@endif
</div>
{{-- SIGNED_DATE --}}
<div class="col-12 col-sm-6 col-md-4 p-0 pe-md-3">
@if($invoiceModel->status === \App\Enums\Invoices\InvoiceStatusEnum::opened AND !$invoiceModel->signed_date)
@include('bo.partials.inputs.date', [
'id' => 'invoiceSigneddate',
'label' => __('Date de signature du :model (client)', [
'model' => __('models.classes.invoices'),
]),
'name' => 'signed_date',
'defaultValue' => false,
'required' => true,
'model' => $invoiceModel ?? null,
'helper' => __('Doît être une date réelle et existante.'),
])
@elseif ($invoiceModel->signed_date)
<p class="fw-bold text-center m-0">
{{ __('Signé le :date (client)', ['date' => \Carbon\Carbon::parse($invoiceModel->signed_date)->format('d/m/Y') ?? "xxx"]) }}
</p>
@endif
</div>
{{-- SENT_ACCOUNTING_DATE --}}
<div class="col-12 col-sm-6 col-md-4 p-0 pe-md-3">
@if(!$invoiceModel->sent_accounting_date)
@include('bo.partials.inputs.timestamp-datetime', [
@if($invoiceModel->status === \App\Enums\Invoices\InvoiceStatusEnum::opened AND !$invoiceModel->sent_accounting_date)
@include('bo.partials.inputs.date', [
'id' => 'invoiceSentaccountingdate',
'label' => __('Date d\'envoi du :model à la compta', [
'label' => __('Date d\'envoi du :model à la comptabilité', [
'model' => __('models.classes.invoices'),
]),
'name' => 'sent_accounting_date',
@ -62,17 +73,40 @@
'model' => $invoiceModel ?? null,
'helper' => __('Doît être une date réelle et existante.'),
])
@else
<p class="fw-bold text-center m-0">{{ __('Envoyé à la compta le :date', ['date' => $invoiceModel->sent_accounting_date]) }}</p>
@elseif ($invoiceModel->sent_accounting_date)
<p class="fw-bold text-center m-0">
{{ __('Envoyé à la comptabilité le :date', ['date' => \Carbon\Carbon::parse($invoiceModel->sent_accounting_date)->format('d/m/Y') ?? "xxx"]) }}
</p>
@endif
</div>
{{-- STATUS --}}
@if ($invoiceModel->status === \App\Enums\Invoices\InvoiceStatusEnum::opened)
<div class="col-12 col-md-6 p-0 pe-md-3">
@include('bo.partials.inputs.tinyint-enum', [
'id' => 'invoiceStatus',
'label' => __('crud.minicrud.associated', [
'entity' => 'État',
]),
'name' => 'status',
'model' => $invoiceModel ?? null,
'required' => true,
'associatedModels' => $invoiceStatusArray,
'helper' => __('crud.minicrud.select', [
'entity' => 'État',
]),
])
@else
<div class="col-12 p-0 pe-md-3">
<p class="fw-bold text-warning text-center m-0 mt-3">{{ $invoiceModel->status->label() }}</p>
@endif
</div>
</div>
</fieldset>
</div>
@endif
{{-- USER ASSOCIATED --}}
<div class="col-12 mb-3">
<fieldset class="row border w-100 h-100 bg-white mx-auto p-3" @if($invoiceModel->signed_date) disabled @endif>
<fieldset class="row border w-100 h-100 bg-white mx-auto p-3" @if($invoiceModel->sent_client_date) disabled @endif>
<legend class="p-0">{{ __('Informations sur la personne associée') }}</legend>
<div class="row w-100 mx-auto p-0">
{{-- FIRSTNAME --}}
@ -126,7 +160,7 @@
</div>
{{-- OBJECT --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="border h-100 bg-white p-3" @if($invoiceModel->signed_date) disabled @endif>
<fieldset class="border h-100 bg-white p-3" @if($invoiceModel->sent_client_date) disabled @endif>
<legend>{{ Str::ucfirst(__('validation.attributes.object')) }}</legend>
<div class="row">
<div class="col-12">
@ -156,7 +190,7 @@
</div>
{{-- TASKS --}}
<div class="col-12 pb-3">
<fieldset class="border bg-white h-100 p-3" @if($invoiceModel->signed_date) disabled @endif>
<fieldset class="border bg-white h-100 p-3" @if($invoiceModel->sent_client_date) disabled @endif>
<legend>{{ __('validation.attributes.tasks') }}</legend>
<div class="row">
<div class="col-12">
@ -164,7 +198,7 @@
$data = [
'companies' => $companyModels,
'packages' => $packageModels,
'taskTypeEnums' => collect(\App\Enums\Invoice\TaskTypeEnum::toArray())->values(),
'taskTypeEnums' => collect(\App\Enums\Invoices\TaskTypeEnum::toArray())->values(),
'taskRowModel' => $taskRowModel,
'tasks' => $invoiceModel->tasks ?? []
];
@ -176,10 +210,13 @@
</div>
{{-- COMMENT --}}
<div class="col-12 pb-3">
<fieldset class="border bg-white h-100 p-3" @if($invoiceModel->signed_date) disabled @endif>
<fieldset class="border bg-white h-100 p-3">
<legend>{{ Str::ucfirst(__('validation.attributes.comment')) }}</legend>
<div class="row mb-3">
<div class="col-12">
@if($invoiceModel->sent_client_date)
<textarea class="form-control" disabled>{{ $invoiceModel->comment }}</textarea>
@else
@include('bo.partials.inputs.text-html', [
'id' => 'invoiceComment',
'label' => __('Commentaire du :model', [
@ -187,18 +224,19 @@
]),
'name' => 'comment',
'model' => $invoiceModel,
'required' => true,
'required' => false,
'helper' => __('crud.minicrud.text_warning', [
'number' => 15
]),
])
@endif
</div>
</div>
</fieldset>
</div>
{{-- SITE_ID --}}
<div class="col-12 col-md-6 pb-3">
<fieldset class="border bg-white h-100 p-3" @if($invoiceModel->signed_date) disabled @endif>
<fieldset class="border bg-white h-100 p-3" @if($invoiceModel->sent_client_date) disabled @endif>
<legend>{{ Str::singular(trans('models.classes.sites')) }}</legend>
<div class="row">
<div class="col-12">
@ -212,7 +250,7 @@
'routeName' => 'invoices.json-paginate',
'modelRelated' => $siteModels ?? [],
'modelSelected' => $invoiceModel->site ?? [],
'readonly' => ($invoiceModel->signed_date) ? true : false,
'readonly' => ($invoiceModel->sent_client_date) ? true : false,
'placeholder' => __('Rechercher une :model', [
'model' => Str::singular(trans('models.classes.sites')),
]),
@ -227,7 +265,7 @@
</div>
{{-- COMPANY_ID --}}
<div class="col-12 col-md-6 pb-3">
<fieldset class="border bg-white h-100 p-3" @if($invoiceModel->signed_date) disabled @endif>
<fieldset class="border bg-white h-100 p-3" @if($invoiceModel->sent_client_date) disabled @endif>
<legend>{{ Str::singular(trans('models.classes.companies')) }}</legend>
<div class="row">
<div class="col-12">
@ -241,7 +279,7 @@
'routeName' => 'invoices.json-paginate',
'modelRelated' => $companyModels ?? [],
'modelSelected' => $invoiceModel->company ?? [],
'readonly' => ($invoiceModel->signed_date) ? true : false,
'readonly' => ($invoiceModel->sent_client_date) ? true : false,
'placeholder' => __('Rechercher une :model', [
'model' => Str::singular(trans('models.classes.companies')),
]),
@ -268,7 +306,7 @@
'routeName' => 'invoices.json-paginate',
'modelRelated' => $companyModels ?? [],
'modelSelected' => $invoiceModel->companyInvoiced ?? [],
'readonly' => ($invoiceModel->signed_date) ? true : false,
'readonly' => ($invoiceModel->sent_client_date) ? true : false,
'placeholder' => __('Rechercher une :model', [
'model' => Str::singular(trans('models.classes.companies')),
]),
@ -283,7 +321,7 @@
</div>
{{-- SHOW DAYS --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="border h-100 bg-white p-3" @if($invoiceModel->signed_date) disabled @endif>
<fieldset class="border h-100 bg-white p-3" @if($invoiceModel->sent_client_date) disabled @endif>
<legend>{{ __('Visibilité') }}</legend>
<div class="row">
<div class="col-12">
@ -302,6 +340,7 @@
</div>
</div>
@if($invoiceModel->status === \App\Enums\Invoices\InvoiceStatusEnum::opened)
@push('scripts')
<script>
document.addEventListener("DOMContentLoaded", () => {
@ -315,3 +354,4 @@
});
</script>
@endpush
@endif

View file

@ -6,13 +6,29 @@
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center">
@include('breadcrumbs.breadcrumb-body')
<a href="{{ route('bo.invoices.create') }}"
class="btn btn-primary float-right"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.create_model', ['model' => Str::singular(__('models.classes.invoices'))]) }}">
<i class="fa-solid fa-plus"></i>
</a>
<form id="invoiceStatus" action="{{ route('bo.invoices.index') }}">
<div class="input-group justify-content-end">
<select class="form-select w-fit" name="status" id="selectInvoiceStatus">
@foreach (\App\Enums\Invoices\InvoiceStatusEnum::toArray() as $invoiceStatusEnum)
<option value="{{ $invoiceStatusEnum->value }}" @if($status === $invoiceStatusEnum->value) selected @endif>
{{ $invoiceStatusEnum->label }}
</option>
@endforeach
</select>
@push('scripts')
<script nonce="{{ $nonce }}">document.getElementById('selectInvoiceStatus').addEventListener('change', function (e) {
document.getElementById('invoiceStatus').submit();
});</script>
@endpush
<a href="{{ route('bo.invoices.create') }}"
class="btn btn-primary float-right"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.create_model', ['model' => Str::singular(__('models.classes.invoices'))]) }}">
<i class="fa-solid fa-plus"></i>
</a>
</div>
</form>
</div>
@include('bo.modules.search-bar')
<table class="table table-hover">

View file

@ -7,7 +7,7 @@
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-3">
<div class="d-flex flex-row align-items-start">
<a href="{{ route('bo.invoices.index') }}"
<a href="{{ route('bo.invoices.index', ['status' => \App\Enums\Invoices\InvoiceStatusEnum::opened]) }}"
class="btn btn-primary text-decoration-none m-0 me-2"
data-bs-toggle="tooltip"
data-bs-placement="top"

View file

@ -59,6 +59,34 @@
</div>
</fieldset>
</div>
{{-- GITLAB_PROJECT_ID --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="border h-100 bg-white p-3">
<legend>{{ Str::ucfirst(__('validation.attributes.gitlab_project_id')) }}</legend>
<div class="row">
<div class="col-12 mb-3">
@include('bo.partials.inputs.int-number', [
'id' => 'siteGitlabProject',
'label' => __(':field du :model', [
'field' => Str::ucfirst(__('validation.attributes.gitlab_project_id')),
'model' => Str::singular(__('models.classes.sites')),
]),
'name' => 'gitlab_project_id',
'required' => true,
'model' => $siteModel ?? null,
'helper' => __('validation.integer', [
'attribute' => __(':field du :model', [
'field' => Str::ucfirst(__('validation.attributes.gitlab_project_id')),
'model' => Str::singular(__('models.classes.sites')),
]),
]),
'step' => 1,
'min' => 1
])
</div>
</div>
</fieldset>
</div>
</div>
@push('scripts')

View file

@ -22,7 +22,7 @@ $userPr = Auth::user()->sites ?? collect([]);
<a href="{{ route('bo.labels.index') }}" class="btn btn-info"><i class="fa-solid fa-tags"></i></a>
@endcan
<a href="{{ route('bo.ticket.create') }}" class="btn btn-primary"><i class="fa-solid fa-square-plus"></i>&nbsp;&nbsp;{{ __('ticket') }}</a>
@if(session('tickets.st') == \App\Enums\Tickets\TicketStatusEnum::archived)
@if(session('tickets.st') == \App\Enums\Tickets\TicketStatusEnum::archived->value)
<a href="{{ route('bo.ticket.index', ['sts' => -1 ]) }}" class="btn btn-success"><i class="fa-solid fa-inbox"></i></a>
@else
<a href="{{ route('bo.ticket.index', ['st' => \App\Enums\Tickets\TicketStatusEnum::archived]) }}" class="float-end btn btn-secondary"><i class="fa-solid fa-box-archive"></i></a>
@ -172,7 +172,7 @@ $userPr = Auth::user()->sites ?? collect([]);
<td>
<span class="ticket-index-labels">
@foreach ($ticket->labels as $label)
<span class="badge label colorize" data-color="{{ $label->color }}">{{ $label->name }}</span>
<span class="badge label colorize" data-color="{{ $label->color }}">{{ $label->name }}</span>
@endforeach
</span>
</td>

View file

@ -256,4 +256,18 @@
</ul>
</div>
@endif
@can('createGitlabIssue', $ticket)
<form action="{{ route('bo.ticket.create-issue', $ticket) }}" method="POST" id="form_issue" class="col d-flex justify-content-end">
@csrf
<button class="btn btn-warning align-end" type="submit">{{ __("Créer l'issue Gitlab") }}</button>
</form>
@else
@if (!\is_null($ticket->gitlab_issue))
<br>
<p>
{{ __("Lien vers l'issue Gitlab : ")}}
<a href="{{ $ticket->gitlab_issue}}" target="_blank">{{ __("Ouvrir") }}</a>
</p>
@endif
@endcan
@endsection

View file

@ -0,0 +1,15 @@
<label for="{{ $id }}">
<b>{{ $label }}</b>
</label>
<input id="{{ $id }}"
name="{{ $name }}"
class="form-control"
type="date"
data-form-type="date"
aria-describedby="{{ "{$id}Help" }}"
value="{{ old($name, ((isset($model) and $model->{$name}) ? ($model->{$name}->format('Y-m-d') ?? ($defaultValue ? Carbon\Carbon::today() : '')) : ($defaultValue ? Carbon\Carbon::today() : ''))) }}">
@include('modules.input-error', [
'inputName' => $name,
'helperId' => "{$id}Help",
'helper' => $helper
])

View file

@ -1,5 +1,5 @@
@if (count($breadcrumbs))
<ol class="breadcrumb flex-nowrap border-0 w-100 p-0 m-0">
<ol class="breadcrumb flex-nowrap border-0 w-fit p-0 m-0">
@foreach ($breadcrumbs as $breadcrumb)
@if ($breadcrumb->url && !$loop->last)
<li class="breadcrumb-item d-flex h2 fw-bold m-0">

View file

@ -31,7 +31,7 @@
<a class="dropdown-item @if(Route::is('bo.projects.*'))active @endif" href="{{ route('bo.projects.index') }}">{{ __('models.classes.projects') }}</a>
@endcan
@can('isAdmin')
<a class="dropdown-item @if(Route::is('bo.invoices.*'))active @endif" href="{{ route('bo.invoices.index') }}">{{ __('models.classes.invoices') }}</a>
<a class="dropdown-item @if(Route::is('bo.invoices.*'))active @endif" href="{{ route('bo.invoices.index', ['status' => \App\Enums\Invoices\InvoiceStatusEnum::opened]) }}">{{ __('models.classes.invoices') }}</a>
@endcan
@can('isAdmin')
<a class="dropdown-item @if(Route::is('bo.companies.*'))active @endif" href="{{ route('bo.companies.index') }}">{{ __('models.classes.companies') }}</a>

View file

@ -1,5 +1,6 @@
<?php
use App\Enums\Invoices\InvoiceStatusEnum;
use App\Models\Brand;
use App\Models\Company;
use App\Models\Dutyhour;
@ -98,7 +99,7 @@ Breadcrumbs::for('bo.companies.edit', function (Generator $trail, Company $compa
// * INVOICES
Breadcrumbs::for('bo.invoices.index', function (Generator $trail) {
$trail->push(__('models.classes.invoices'), route('bo.invoices.index'));
$trail->push(__('models.classes.invoices'), route('bo.invoices.index', ['status' => InvoiceStatusEnum::opened]));
});
Breadcrumbs::for('bo.invoices.create', function (Generator $trail) {
$trail->parent('bo.invoices.index');

View file

@ -90,7 +90,10 @@ Route::middleware('admin-secure')
// * TICKETS
Route::get('/tickets/json/paginate', [TicketController::class, 'jsonPaginate'])
->name('tickets.json-paginate');
->name('tickets.json-paginate');
Route::post('/ticket/{ticket}/create-issue', [TicketController::class, 'createGitlabIssue'])
->middleware('can:createGitlabIssue,ticket')
->name('ticket.create-issue');
// * SCHEDULE
Route::prefix('/schedule')