Merge branch 'trunk'
This commit is contained in:
commit
cd0cbd45aa
159 changed files with 6192 additions and 2216 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
|
@ -1,3 +1,22 @@
|
|||
### 31.05.2022 -> 1.2.0
|
||||
- Ajout de la fonctionnalitée d'estimations
|
||||
|
||||
### 26.05.2022 -> 1.1.0
|
||||
- Correction de regression sur macro planning
|
||||
- Correction de regression sur droits planning
|
||||
- Ajout de fonctionnalité Label
|
||||
- Ajout de fonctionalité assigner un ticket
|
||||
- Rafactorisation du code ticket
|
||||
- Refonte de tickets.index
|
||||
- Ajout CSP report URI
|
||||
- Remove external JS and CSS dependencies
|
||||
- Use Vuejs runtime
|
||||
- Add xss sanitizer
|
||||
- Normalize Laravel mix + add cleaner
|
||||
- Update npm deps
|
||||
- Upgrade bootswatch theme
|
||||
- ADD Secure HEADERS
|
||||
|
||||
### 23.05.2022 -> 1.0.9
|
||||
- Correction d'erreurs multiples
|
||||
- Fonctionnalité macro planning
|
||||
|
|
|
|||
56
app/Casts/HtmlColor.php
Normal file
56
app/Casts/HtmlColor.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
|
||||
/**
|
||||
* Custom cast Html Color
|
||||
* https://laravel.com/docs/8.x/eloquent-mutators#custom-casts
|
||||
*/
|
||||
class HtmlColor implements CastsAttributes
|
||||
{
|
||||
/**
|
||||
* Cast the given value.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
|
||||
*/
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
return "#{$this->zeropad(dechex($value), 6)}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given value for storage.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array $attributes
|
||||
* @return integer
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
*/
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
return \intval(hexdec(ltrim($value, '#')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad with zeros a numeric string
|
||||
*
|
||||
* @param string $num
|
||||
* @param integer $lim
|
||||
* @return string
|
||||
*/
|
||||
private function zeropad(string $num, int $lim): string
|
||||
{
|
||||
return (strlen($num) >= $lim) ? $num : $this->zeropad("0" . $num, $lim);
|
||||
}
|
||||
}
|
||||
38
app/Enums/Credit/AccountType.php
Normal file
38
app/Enums/Credit/AccountType.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Credit;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnumRoutable;
|
||||
|
||||
/**
|
||||
* @method static self development()
|
||||
* @method static self seo()
|
||||
*/
|
||||
class AccountType extends BaseEnumRoutable
|
||||
{
|
||||
/**
|
||||
* Enum values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'development' => 0,
|
||||
'seo' => 1
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'development' => trans('Jours de développement'),
|
||||
'seo' => trans('Jours de seo')
|
||||
];
|
||||
}
|
||||
}
|
||||
38
app/Enums/Credit/BalanceOperation.php
Normal file
38
app/Enums/Credit/BalanceOperation.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Credit;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnumRoutable;
|
||||
|
||||
/**
|
||||
* @method static self credit()
|
||||
* @method static self debit()
|
||||
*/
|
||||
class BalanceOperation extends BaseEnumRoutable
|
||||
{
|
||||
/**
|
||||
* Enum values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'credit' => 0,
|
||||
'debit' => 1
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'credit' => trans('Créditer un compte'),
|
||||
'debit' => trans('Débiter un compte')
|
||||
];
|
||||
}
|
||||
}
|
||||
41
app/Enums/Credit/EstimationStatus.php
Normal file
41
app/Enums/Credit/EstimationStatus.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Credit;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnumRoutable;
|
||||
|
||||
/**
|
||||
* @method static self pending()
|
||||
* @method static self accepted()
|
||||
* @method static self refused()
|
||||
*/
|
||||
class EstimationStatus extends BaseEnumRoutable
|
||||
{
|
||||
/**
|
||||
* Enum values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'pending' => 0,
|
||||
'accepted' => 1,
|
||||
'refused' => 2
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'pending' => trans('En attente'),
|
||||
'accepted' => trans('Acceptée'),
|
||||
'refused' => trans('Refusée'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,13 @@ use Kwaadpepper\Enum\BaseEnumRoutable;
|
|||
* @method static self admin()
|
||||
* @method static self client()
|
||||
*/
|
||||
|
||||
class Role extends BaseEnumRoutable
|
||||
{
|
||||
/**
|
||||
* Enum values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
|
|
@ -21,6 +25,11 @@ class Role extends BaseEnumRoutable
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ use Kwaadpepper\Enum\BaseEnumRoutable;
|
|||
*/
|
||||
class TicketStatus extends BaseEnumRoutable
|
||||
{
|
||||
/**
|
||||
* Enum values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
|
|
@ -40,6 +45,11 @@ class TicketStatus extends BaseEnumRoutable
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
|
|
@ -58,4 +68,40 @@ class TicketStatus extends BaseEnumRoutable
|
|||
'archived' => trans('Archivé'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum classes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function classes(): array
|
||||
{
|
||||
return [
|
||||
'new' => 'text-danger',
|
||||
'estimateTodo' => 'text-info',
|
||||
'estimateValidated' => 'text-info',
|
||||
'estimateDone' => 'text-info',
|
||||
'toPlan' => 'text-info',
|
||||
'planned' => 'text-info',
|
||||
'inprogress' => 'text-dark',
|
||||
'waiting' => 'text-white bg-danger',
|
||||
'waitingValidation' => 'text-warning bg-danger',
|
||||
'processed' => trans('Traité'),
|
||||
'recette' => 'text-primary',
|
||||
'production' => 'text-success',
|
||||
'archived' => 'text-secondary',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @return integer|string
|
||||
*/
|
||||
public function __get(string $attribute)
|
||||
{
|
||||
if ($attribute === 'class') {
|
||||
return $this->classes()[$this->getDefinition()];
|
||||
}
|
||||
return parent::__get($attribute);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ use Kwaadpepper\Enum\BaseEnumRoutable;
|
|||
*/
|
||||
class TicketType extends BaseEnumRoutable
|
||||
{
|
||||
/**
|
||||
* Enum values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
|
|
@ -22,6 +27,11 @@ class TicketType extends BaseEnumRoutable
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
|
|
@ -31,4 +41,31 @@ class TicketType extends BaseEnumRoutable
|
|||
'newsletter' => trans('Demande newsletter')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum classes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function classes(): array
|
||||
{
|
||||
return [
|
||||
'bug' => 'bg-danger',
|
||||
'evolution' => 'bg-success',
|
||||
'br' => 'bg-primary',
|
||||
'newsletter' => 'bg-warning'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @return integer|string
|
||||
*/
|
||||
public function __get(string $attribute)
|
||||
{
|
||||
if ($attribute === 'class') {
|
||||
return $this->classes()[$this->getDefinition()];
|
||||
}
|
||||
return parent::__get($attribute);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
51
app/Http/Controllers/BrandController.php
Normal file
51
app/Http/Controllers/BrandController.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Brand;
|
||||
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
|
||||
use Kwaadpepper\CrudPolicies\Traits\CrudController;
|
||||
|
||||
class BrandController extends Controller
|
||||
{
|
||||
use CrudController {
|
||||
__construct as __CrudConstructor;
|
||||
index as crudIndex;
|
||||
}
|
||||
|
||||
/** @var Account */
|
||||
public static $modelClass = Brand::class;
|
||||
|
||||
/**
|
||||
* Crud Layout
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $layout = 'layout.app';
|
||||
|
||||
/**
|
||||
* ProjectController
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->__CrudConstructor();
|
||||
$this->authorizeResource(Brand::class, 'brand');
|
||||
}
|
||||
|
||||
/**
|
||||
* Crud index view
|
||||
*
|
||||
* @param \Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest $request
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function index(CrudIndexRequest $request)
|
||||
{
|
||||
/** @var \Illuminate\View\View */
|
||||
$view = $this->crudIndex($request);
|
||||
if ($request->ajax()) {
|
||||
return $view;
|
||||
}
|
||||
return view('brands.index', $view->getData());
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ use Illuminate\Foundation\Validation\ValidatesRequests;
|
|||
use Illuminate\Routing\Controller as BaseController;
|
||||
use App\Enums\TicketStatus;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
|
|
@ -55,9 +56,9 @@ class Controller extends BaseController
|
|||
/** @var \App\Models\User|null */
|
||||
$user = Auth::user();
|
||||
if (!is_null($user) and !$user->isAdmin()) {
|
||||
$ticketsNew = $ticketsNew->where('is_internal', 0)->whereNotIn('site_id', $ignore);
|
||||
$ticketsWaiting = $ticketsWaiting->where('is_internal', 0)->whereNotIn('site_id', $ignore);
|
||||
$ticketsInProgress = $ticketsInProgress->where('is_internal', 0)->whereNotIn('site_id', $ignore);
|
||||
$ticketsNew = $ticketsNew->where('is_internal', false)->whereNotIn('site_id', $ignore);
|
||||
$ticketsWaiting = $ticketsWaiting->where('is_internal', false)->whereNotIn('site_id', $ignore);
|
||||
$ticketsInProgress = $ticketsInProgress->where('is_internal', false)->whereNotIn('site_id', $ignore);
|
||||
}
|
||||
|
||||
View::share('ticketsNew', $ticketsNew->count());
|
||||
|
|
@ -67,4 +68,68 @@ class Controller extends BaseController
|
|||
return $next($request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Search query on specified fields
|
||||
* splitting search into words or using whole sentence
|
||||
*
|
||||
* @param Builder $query The eloquent query builder.
|
||||
* @param string $search The query string.
|
||||
* @param callable $searchQuery The query string.
|
||||
* @param string ...$fields Either a simple array or a string
|
||||
* in case of a string the two fields would be concatenate to make the search.
|
||||
* @return Builder
|
||||
*/
|
||||
protected function searchQuery(
|
||||
Builder $query,
|
||||
string $search,
|
||||
callable $searchQuery = null,
|
||||
string ...$fields
|
||||
): Builder {
|
||||
if (count($fields)) {
|
||||
// Search using words.
|
||||
$words = explode(' ', $search);
|
||||
$query->where(function ($query) use ($searchQuery, $words, $search, $fields) {
|
||||
foreach ($words as $word) {
|
||||
if (!strlen($word)) {
|
||||
continue;
|
||||
}
|
||||
$this->searchOnFields($query, $word, $fields);
|
||||
} //end foreach
|
||||
// Search whole using whole sentence.
|
||||
$this->searchOnFields($query, $search, $fields);
|
||||
if ($searchQuery) {
|
||||
$searchQuery($query);
|
||||
}
|
||||
});
|
||||
} //end if
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query search on all fields
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param string $search
|
||||
* @param array $fields
|
||||
* @return Builder
|
||||
*/
|
||||
private function searchOnFields(Builder $query, string $search, array $fields): Builder
|
||||
{
|
||||
$table = $query->getModel()->getTable();
|
||||
foreach ($fields as $f) {
|
||||
$f = str_replace('?', '\?', $f);
|
||||
if (is_array($f)) {
|
||||
$query->orWhereRaw(
|
||||
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($f)->map(function ($q) use ($table) {
|
||||
return "`$table`.`$q`";
|
||||
})->implode(', \' \', ')),
|
||||
["%$search%"]
|
||||
);
|
||||
} else {
|
||||
$query->orWhereRaw("UPPER(`$table`.`$f`) LIKE UPPER(?)", ["%$search%"]);
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
118
app/Http/Controllers/Credit/AccountController.php
Normal file
118
app/Http/Controllers/Credit/AccountController.php
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Credit;
|
||||
|
||||
use App\Enums\Credit\BalanceOperation;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Credit\AddCreditToAccountRequest;
|
||||
use App\Models\Brand;
|
||||
use App\Models\Credit\Account;
|
||||
use App\Models\Credit\CreditLog;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
|
||||
use Kwaadpepper\CrudPolicies\Traits\CrudController;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
use CrudController {
|
||||
__construct as __CrudConstructor;
|
||||
index as crudIndex;
|
||||
}
|
||||
|
||||
/** @var Account */
|
||||
public static $modelClass = Account::class;
|
||||
|
||||
/**
|
||||
* Crud Layout
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $layout = 'layout.app';
|
||||
|
||||
/**
|
||||
* Index query middleware
|
||||
*
|
||||
* @param Builder $query
|
||||
* @return Builder
|
||||
*/
|
||||
public function indexQuery(Builder $query): Builder
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
if (!$user->isAdmin()) {
|
||||
// Show only users brand(s) accounts.
|
||||
$brands = $user->sites()->with('brand')->get()->pluck('brand')
|
||||
->filter(function ($v) {
|
||||
return !\is_null($v);
|
||||
});
|
||||
return $query->whereHas('brand', function ($q) use ($brands) {
|
||||
$q->whereIn('id', $brands->pluck('id'));
|
||||
});
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crud index view
|
||||
*
|
||||
* @param \Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest $request
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function index(CrudIndexRequest $request)
|
||||
{
|
||||
/** @var \Illuminate\View\View */
|
||||
$view = $this->crudIndex($request);
|
||||
if ($request->ajax()) {
|
||||
return $view;
|
||||
}
|
||||
return view('accounts.index', $view->getData());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Before store model middleware
|
||||
*
|
||||
* @param Account $model
|
||||
* @return void
|
||||
*/
|
||||
public function storeModel(Account &$model)
|
||||
{
|
||||
$model->brand()->associate(\request()->brand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit
|
||||
*
|
||||
* @param \App\Models\Brand $brand
|
||||
* @param \App\Models\Account $account
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
*/
|
||||
public function credit(Brand $brand, Account $account)
|
||||
{
|
||||
return view('accounts.credit', compact('brand', 'account'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit Account
|
||||
*
|
||||
* @param \App\Http\Requests\Credit\AddCreditToAccountRequest $request
|
||||
* @param \App\Models\Brand $brand
|
||||
* @param \App\Models\Account $account
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
*/
|
||||
public function creditSubmit(AddCreditToAccountRequest $request, Brand $brand, Account $account)
|
||||
{
|
||||
$log = new CreditLog();
|
||||
$log->account()->associate($account);
|
||||
$log->type = BalanceOperation::credit();
|
||||
$log->amount = \strval($request->amount);
|
||||
if ($log->saveOrFail()) {
|
||||
return \response()->redirectToRoute('brands.credit_accounts.index', $brand)
|
||||
->with('info', trans('Le compte à bien été crédité'));
|
||||
}
|
||||
return back();
|
||||
}
|
||||
}
|
||||
55
app/Http/Controllers/Credit/CreditLogController.php
Normal file
55
app/Http/Controllers/Credit/CreditLogController.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Credit;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Credit\CreditLog;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
|
||||
use Kwaadpepper\CrudPolicies\Traits\CrudController;
|
||||
|
||||
class CreditLogController extends Controller
|
||||
{
|
||||
use CrudController {
|
||||
__construct as __CrudConstructor;
|
||||
index as crudIndex;
|
||||
}
|
||||
|
||||
/** @var CreditLog */
|
||||
public static $modelClass = CreditLog::class;
|
||||
|
||||
/**
|
||||
* Crud Layout
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $layout = 'layout.app';
|
||||
|
||||
/**
|
||||
* Index query middleware
|
||||
*
|
||||
* @param Builder $query
|
||||
* @return Builder
|
||||
*/
|
||||
public function indexQuery(Builder $query): Builder
|
||||
{
|
||||
$query->orderBy('created_at', 'DESC');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crud index view
|
||||
*
|
||||
* @param \Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest $request
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function index(CrudIndexRequest $request)
|
||||
{
|
||||
/** @var \Illuminate\View\View */
|
||||
$view = $this->crudIndex($request);
|
||||
if ($request->ajax()) {
|
||||
return $view;
|
||||
}
|
||||
return view('credit_logs.index', $view->getData());
|
||||
}
|
||||
}
|
||||
102
app/Http/Controllers/Credit/EstimationController.php
Normal file
102
app/Http/Controllers/Credit/EstimationController.php
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Credit;
|
||||
|
||||
use App\Enums\Credit\EstimationStatus;
|
||||
use App\Enums\TicketStatus;
|
||||
use App\Events\NotifyAdmin;
|
||||
use App\Events\NotifySubscriptions;
|
||||
use App\Events\TicketStatusChanged;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Credit\AnswerEstimateRequest;
|
||||
use App\Http\Requests\Credit\CreateEstimateRequest;
|
||||
use App\Models\Credit\Estimation;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class EstimationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show estimate Form
|
||||
*
|
||||
* @param \App\Models\Ticket $ticket
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function estimate(Ticket $ticket)
|
||||
{
|
||||
$this->authorize('estimate', $ticket);
|
||||
return view('estimations.create', \compact('ticket'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Ticket estimation
|
||||
*
|
||||
* @param \App\Http\Requests\Credit\CreateEstimateRequest $request
|
||||
* @param \App\Models\Ticket $ticket
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function estimateSubmit(CreateEstimateRequest $request, Ticket $ticket)
|
||||
{
|
||||
return DB::transaction(function () use ($request, $ticket) {
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
$estimate = new Estimation($request->validated());
|
||||
$estimate->ticket()->associate($ticket);
|
||||
$estimate->estimator()->associate($user);
|
||||
$ticket->status = TicketStatus::waitingValidation();
|
||||
$ticket->saveQuietly();
|
||||
if ($estimate->saveOrFail()) {
|
||||
$message = sprintf(
|
||||
'<b>%s</b> a modifié le statut en <b>%s</b>, Une estimation de %s jours à été ajoutée',
|
||||
$user->name,
|
||||
$ticket->status->label,
|
||||
\number_format(\floatval($request->amount), 1, ',', '')
|
||||
);
|
||||
event(new TicketStatusChanged($ticket, $ticket->status, $user));
|
||||
event(new NotifySubscriptions($ticket, $message));
|
||||
event(new NotifyAdmin($ticket, $message));
|
||||
return response()->redirectToRoute('ticket.show', $ticket)
|
||||
->with('info', trans('L\'estimation à bien été ajouté'));
|
||||
}
|
||||
return \back();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Answer ticket estimation
|
||||
*
|
||||
* @param \App\Http\Requests\Credit\AnswerEstimateRequest $request
|
||||
* @param \App\Models\Ticket $ticket
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function estimatePendingSubmit(AnswerEstimateRequest $request, Ticket $ticket)
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
/** @var \App\Models\Credit\Estimation */
|
||||
$estimation = $ticket->estimation;
|
||||
if ($request->answer) {
|
||||
$ticket->status = TicketStatus::toPlan();
|
||||
$estimation->status = EstimationStatus::accepted();
|
||||
} else {
|
||||
$ticket->status = TicketStatus::archived();
|
||||
$estimation->status = EstimationStatus::refused();
|
||||
}
|
||||
$estimation->validator()->associate($user);
|
||||
if ($estimation->saveOrFail() and $ticket->saveOrFail()) {
|
||||
$message = sprintf(
|
||||
'Une réponse à été fourni par %s pour l\'estimation du ticket n°%d : %s',
|
||||
$user->name,
|
||||
$ticket->id,
|
||||
$estimation->status->label
|
||||
);
|
||||
event(new TicketStatusChanged($ticket, $ticket->status, $user));
|
||||
event(new NotifySubscriptions($ticket, $message));
|
||||
event(new NotifyAdmin($ticket, $message));
|
||||
return response()->redirectToRoute('ticket.show', $ticket)
|
||||
->with('info', trans('Votre réponse à été enregistrée'));
|
||||
}
|
||||
return \back();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,11 @@ use Parsedown;
|
|||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show Home page
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function home()
|
||||
{
|
||||
$Parsedown = new Parsedown();
|
||||
|
|
|
|||
21
app/Http/Controllers/LabelController.php
Normal file
21
app/Http/Controllers/LabelController.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Label;
|
||||
use Kwaadpepper\CrudPolicies\Traits\CrudController;
|
||||
|
||||
class LabelController extends Controller
|
||||
{
|
||||
use CrudController;
|
||||
|
||||
/** @var Label */
|
||||
public static $modelClass = Label::class;
|
||||
|
||||
/**
|
||||
* Crud Layout
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $layout = 'layout.app';
|
||||
}
|
||||
|
|
@ -17,6 +17,15 @@ use Carbon\CarbonImmutable;
|
|||
*/
|
||||
class ScheduleController extends Controller
|
||||
{
|
||||
/**
|
||||
* ScheduleController
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware('is-admin');
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$date = CarbonImmutable::now()->startOfWeek();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ class SiteController extends Controller
|
|||
index as crudIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* SiteController
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
|
@ -23,8 +26,19 @@ class SiteController extends Controller
|
|||
/** @var Site */
|
||||
public static $modelClass = Site::class;
|
||||
|
||||
/**
|
||||
* Crud Layout
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $layout = 'layout.app';
|
||||
|
||||
/**
|
||||
* Site.index crud
|
||||
*
|
||||
* @param CrudIndexRequest $request
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function index(CrudIndexRequest $request)
|
||||
{
|
||||
/** @var \Illuminate\View\View */
|
||||
|
|
|
|||
|
|
@ -6,39 +6,48 @@ use App\Enums\TicketStatus;
|
|||
use App\Enums\TicketType;
|
||||
use App\Events\NotifySubscriptions;
|
||||
use App\Events\TicketStatusChanged;
|
||||
use App\Http\Requests\AssignTicketToUserRequest;
|
||||
use App\Http\Requests\ChangeTicketStatus;
|
||||
use App\Http\Requests\CreateMessage;
|
||||
use App\Http\Requests\UpdateMessage;
|
||||
use App\Http\Requests\SortTickets;
|
||||
use App\Http\Requests\StoreTicket;
|
||||
use App\Http\Requests\UpdateMessage;
|
||||
use App\Http\Requests\UpdateTicket;
|
||||
use App\Lib\ToolboxHelper;
|
||||
use App\Mail\NewTicket;
|
||||
use App\Models\Message;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class TicketController extends Controller
|
||||
{
|
||||
/**
|
||||
* TicketController
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->authorizeResource(Ticket::class, 'ticket');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tickets List
|
||||
*
|
||||
* @param SortTickets $request
|
||||
* @return \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function index(SortTickets $request)
|
||||
{
|
||||
// handle ajax
|
||||
// * Handle Ajax
|
||||
if ($request->ajax()) {
|
||||
$tickets = Ticket::with('user');
|
||||
$tickets = Ticket::with(['user', 'assignedUser']);
|
||||
if ($request->s) {
|
||||
$tickets = $tickets->where('text', 'LIKE', "%$request->s%")
|
||||
->orWhere('id', $request->s)
|
||||
|
|
@ -64,20 +73,25 @@ class TicketController extends Controller
|
|||
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
$tickets = Ticket::with('site')->has('site');
|
||||
$tickets = Ticket::with(['site', 'labels']);
|
||||
if (!$user->isAdmin()) {
|
||||
$tickets = $tickets->whereHas('site', function ($q) use ($user) {
|
||||
$tickets->whereHas('site', function ($q) use ($user) {
|
||||
$q->whereHas('users', function ($q) use ($user) {
|
||||
$q->whereId($user->id);
|
||||
});
|
||||
});
|
||||
$tickets = $tickets->where('is_internal', 0);
|
||||
}
|
||||
$tickets = $tickets->where('status', ($request->st == TicketStatus::archived()) ?
|
||||
'=' : '<>', TicketStatus::archived());
|
||||
if ($request->search) {
|
||||
$tickets = $this->searchTicket($tickets, $request);
|
||||
$tickets->where('is_internal', false);
|
||||
} else {
|
||||
$tickets->has('site');
|
||||
}
|
||||
|
||||
$tickets->where(
|
||||
'status',
|
||||
($request->st == TicketStatus::archived()) ? '=' : '<>',
|
||||
TicketStatus::archived()
|
||||
);
|
||||
|
||||
$tickets = $this->searchTicket($tickets, $request);
|
||||
$tickets = $this->filterTickets($tickets, $request);
|
||||
$tickets = $tickets->paginate();
|
||||
|
||||
|
|
@ -87,14 +101,394 @@ class TicketController extends Controller
|
|||
return view('ticket.index', compact('tickets', 'sites', 'status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Ticket
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Ticket $ticket
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function show(Request $request, Ticket $ticket)
|
||||
{
|
||||
if ($request->ajax()) {
|
||||
return \response()->json([
|
||||
'label' => sprintf(
|
||||
'Ticket %d (%s): %s',
|
||||
$ticket->id,
|
||||
$ticket->user ? $ticket->user->email : __('Utilisateur supprimé'),
|
||||
Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35)
|
||||
),
|
||||
'value' => $ticket->id
|
||||
]);
|
||||
}
|
||||
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
|
||||
if (!$user->can('update', $ticket)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
if ($ticket->is_internal && !$user->can('changeInternalStatus', $ticket)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$messages = $ticket->messages()->orderBy('created_at', 'DESC')->get();
|
||||
$isSubscribed = (bool)$ticket->subscriptions()->where('user_id', $user->id)->count();
|
||||
return view('ticket.show', compact('ticket', 'messages', 'isSubscribed'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show create Form
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
if ($user->isAdmin()) {
|
||||
$sites = Site::orderBy('name')->get();
|
||||
} else {
|
||||
$sites = $user->sites;
|
||||
}
|
||||
|
||||
if (!count($sites)) {
|
||||
return redirect()->route('sites.create')
|
||||
->with('warning', trans('Vous devez tout d\'abord créer un site pour y ajouter un ticket'));
|
||||
}
|
||||
return view('ticket.create', compact('sites'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ticket
|
||||
*
|
||||
* @param StoreTicket $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(StoreTicket $request)
|
||||
{
|
||||
try {
|
||||
return DB::transaction(function () use ($request) {
|
||||
/** @var \App\Models\Site */
|
||||
$site = Site::findOrFail($request->site_id);
|
||||
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
|
||||
$ticket = new Ticket($request->validated());
|
||||
|
||||
/** @var \Illuminate\Http\UploadedFile|null */
|
||||
$fileTicket = $request->file_ticket;
|
||||
/** @var \Illuminate\Http\UploadedFile|null */
|
||||
$fileTicket2 = $request->file_ticket2;
|
||||
|
||||
if ($fileTicket) {
|
||||
$ticket->file = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket);
|
||||
}
|
||||
if ($fileTicket2) {
|
||||
$ticket->file2 = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket2);
|
||||
}
|
||||
|
||||
if ($request->labels) {
|
||||
$ticket->labels()->sync($request->labels);
|
||||
}
|
||||
$ticket->user()->associate(auth()->user());
|
||||
$ticket->site()->associate($site);
|
||||
$ticket->saveOrFail();
|
||||
|
||||
$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');
|
||||
}
|
||||
|
||||
return redirect()->route('ticket.show', $ticket)
|
||||
->with('success', trans('Ticket enregistré !'));
|
||||
});
|
||||
} catch (\ErrorException $e) {
|
||||
report($e);
|
||||
Session::flash('error', trans('Une erreur est survenue'));
|
||||
} //end try
|
||||
|
||||
return \back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show update form
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function edit(Ticket $ticket)
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
if ($user->isAdmin()) {
|
||||
$sites = Site::orderBy('name')->get();
|
||||
} else {
|
||||
$sites = $user->sites;
|
||||
}
|
||||
return view('ticket.edit', compact('ticket', 'sites'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Model
|
||||
*
|
||||
* @param UpdateTicket $request
|
||||
* @param Ticket $ticket
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(UpdateTicket $request, Ticket $ticket)
|
||||
{
|
||||
$ticket->fill($request->validated());
|
||||
|
||||
try {
|
||||
return DB::transaction(function () use ($request, $ticket) {
|
||||
/** @var \App\Models\Site */
|
||||
$site = Site::findOrFail($request->site_id);
|
||||
|
||||
/** @var \Illuminate\Http\UploadedFile|null */
|
||||
$fileTicket = $request->file_ticket;
|
||||
/** @var \Illuminate\Http\UploadedFile|null */
|
||||
$fileTicket2 = $request->file_ticket2;
|
||||
|
||||
if ($fileTicket) {
|
||||
$ticket->file = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket);
|
||||
}
|
||||
if ($fileTicket2) {
|
||||
$ticket->file2 = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket2);
|
||||
}
|
||||
|
||||
if ($request->labels) {
|
||||
$ticket->labels()->sync($request->labels);
|
||||
}
|
||||
|
||||
$ticket->site()->associate($site);
|
||||
$ticket->saveOrFail();
|
||||
|
||||
return redirect()->route('ticket.edit', $ticket->id)
|
||||
->with('success', 'Modifications enregistrées !');
|
||||
});
|
||||
} catch (\ErrorException $e) {
|
||||
report($e);
|
||||
Session::flash('error', trans('Une erreur est survenue'));
|
||||
} //end try
|
||||
|
||||
return \back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change ticket Status
|
||||
*
|
||||
* @param ChangeTicketStatus $request
|
||||
* @param Ticket $ticket
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function changeStatus(ChangeTicketStatus $request, Ticket $ticket)
|
||||
{
|
||||
$ticket->fill($request->validated());
|
||||
$ticket->saveOrFail();
|
||||
|
||||
return back()
|
||||
->with('warning', trans('Le status à été modifié'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Message to the Ticket
|
||||
*
|
||||
* @param CreateMessage $request
|
||||
* @param Ticket $ticket
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function createMessage(CreateMessage $request, Ticket $ticket)
|
||||
{
|
||||
try {
|
||||
return DB::transaction(function () use ($request, $ticket) {
|
||||
/** @var \Illuminate\Http\UploadedFile|null */
|
||||
$fileMessage = $request->file_message;
|
||||
|
||||
$message = new Message($request->validated());
|
||||
$message->user()->associate(auth()->user());
|
||||
$message->ticket()->associate($ticket);
|
||||
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
|
||||
if ($request->subscribe) {
|
||||
if (!$user->subscriptions()->find($ticket->id)) {
|
||||
$user->subscriptions()->attach($ticket->id);
|
||||
}
|
||||
} elseif ($user->subscriptions()->find($ticket->id)) {
|
||||
$user->subscriptions()->detach($ticket->id);
|
||||
}
|
||||
|
||||
$message->saveOrFail();
|
||||
|
||||
if ($fileMessage) {
|
||||
$message->file = ToolboxHelper::storeImage($message->getTable(), $fileMessage);
|
||||
$message->saveOrFail();
|
||||
}
|
||||
return back()
|
||||
->with('success', trans('Votre message à été ajouté'));
|
||||
});
|
||||
} catch (\ErrorException $e) {
|
||||
report($e);
|
||||
Session::flash('error', trans('Une erreur est survenue'));
|
||||
} //end try
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Message
|
||||
*
|
||||
* @param UpdateMessage $request
|
||||
* @param Ticket $ticket
|
||||
* @param Message $message
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
*/
|
||||
public function updateMessage(UpdateMessage $request, Ticket $ticket, Message $message)
|
||||
{
|
||||
$message->message = $request->message;
|
||||
$message->saveOrFail();
|
||||
|
||||
return back()
|
||||
->with('success', trans('Votre message à bien été modifié'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change ticket status to archived
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function setToArchive(Ticket $ticket)
|
||||
{
|
||||
$ticket->status = TicketStatus::archived();
|
||||
$ticket->saveOrFail();
|
||||
return redirect()->route('ticket.index')
|
||||
->with('warning', trans('Le ticket est archivé'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to ticket
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function subscribe(Ticket $ticket)
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
$user->subscriptions()->toggle($ticket->id);
|
||||
if ($user->subscriptions()->where('ticket_id', $ticket->id)->exists()) {
|
||||
return back()
|
||||
->with('success', trans('Vous avez été inscrit'));
|
||||
}
|
||||
return back()->with('warning', trans('Vous avez été désinscrit'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopen ticket
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function reopen(Ticket $ticket)
|
||||
{
|
||||
$message = trans(':ticketName à réouvert le ticket n°:ticketNumber', [
|
||||
'ticketName' => $ticket->user->name,
|
||||
'ticketNumber' => $ticket->id,
|
||||
]);
|
||||
$oldStatus = $ticket->status;
|
||||
$ticket->status = TicketStatus::inprogress();
|
||||
$ticket->saveQuietly();
|
||||
event(new TicketStatusChanged($ticket, $oldStatus, null, $message));
|
||||
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
|
||||
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
$user->subscriptions()->toggle($ticket->id);
|
||||
|
||||
return back()->with('warning', trans('Votre ticket à été réouvert.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign ticket to user ticket
|
||||
*
|
||||
* @param AssignTicketToUserRequest $request
|
||||
* @param Ticket $ticket
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function assignTo(AssignTicketToUserRequest $request, Ticket $ticket)
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
|
||||
/** @var \App\Models\User|null */
|
||||
if ($assignedUser = User::whereId($request->assign)->first()) {
|
||||
$message = trans(':admin à assigné le ticket n°:ticketNumber à :assignedAdmin', [
|
||||
'admin' => $user->name,
|
||||
'ticketNumber' => $ticket->id,
|
||||
'assignedAdmin' => $assignedUser->name,
|
||||
]);
|
||||
$ticket->assignedUser()->associate($assignedUser);
|
||||
} else {
|
||||
$message = trans(
|
||||
':admin modifié l\'assignation du ticket n°:ticketNumber, il n\'est plus assigné à quelqu\'un.',
|
||||
[
|
||||
'admin' => $user->name,
|
||||
'ticketNumber' => $ticket->id,
|
||||
]
|
||||
);
|
||||
$ticket->assignedUser()->dissociate();
|
||||
}
|
||||
$ticket->saveQuietly();
|
||||
|
||||
|
||||
/** @var \App\Enums\TicketStatus */
|
||||
$oldStatus = $ticket->status;
|
||||
event(new TicketStatusChanged($ticket, $oldStatus, null, $message));
|
||||
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
|
||||
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
$user->subscriptions()->toggle($ticket->id);
|
||||
|
||||
if ($assignedUser) {
|
||||
return back()->with('info', trans('Le ticket à été assigné à :assignedUser', [
|
||||
'assignedUser' => $assignedUser->name
|
||||
]));
|
||||
}
|
||||
return back()->with('info', trans('Le ticket n\'est plus assigné à personne'));
|
||||
}
|
||||
|
||||
// * PRIVATES
|
||||
|
||||
/**
|
||||
* Filter Tickets request constructor
|
||||
*
|
||||
* @param Builder $tickets
|
||||
* @param Request $request
|
||||
* @return Builder
|
||||
*/
|
||||
private function filterTickets(Builder $tickets, Request $request): Builder
|
||||
{
|
||||
if ($request->rst) {
|
||||
Session::remove('tickets.ass');
|
||||
Session::remove('tickets.st');
|
||||
Session::remove('tickets.pr');
|
||||
Session::remove('tickets.sort_col');
|
||||
Session::remove('tickets.sort_way');
|
||||
}
|
||||
if ($request->assr) {
|
||||
Session::remove('tickets.ass');
|
||||
}
|
||||
if ($request->prs) {
|
||||
Session::remove('tickets.pr');
|
||||
}
|
||||
|
|
@ -113,14 +507,23 @@ class TicketController extends Controller
|
|||
if ($request->st) {
|
||||
Session::put('tickets.st', $request->st);
|
||||
}
|
||||
if ($request->ass) {
|
||||
Session::put('tickets.ass', $request->ass);
|
||||
}
|
||||
Session::save();
|
||||
|
||||
if (Session::has('tickets.pr')) {
|
||||
$tickets = $tickets->where('site_id', Session::get('tickets.pr'));
|
||||
$tickets->where('site_id', Session::get('tickets.pr'));
|
||||
}
|
||||
|
||||
if (Session::has('tickets.ass')) {
|
||||
$tickets->whereHas('assignedUser', function ($q) {
|
||||
$q->whereId(Session::get('tickets.ass'));
|
||||
});
|
||||
}
|
||||
|
||||
if (Session::has('tickets.st')) {
|
||||
$tickets = $tickets->where('status', Session::get('tickets.st'));
|
||||
$tickets->where('status', Session::get('tickets.st'));
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -128,264 +531,65 @@ class TicketController extends Controller
|
|||
Session::has('tickets.sort_way') and
|
||||
in_array(Session::get('tickets.sort_col'), [
|
||||
'id', 'site_id', 'created_at', 'created_by',
|
||||
'type', 'status'
|
||||
'type', 'status', 'assigned_to'
|
||||
])
|
||||
) {
|
||||
$tickets = $tickets->orderBy(
|
||||
$tickets->orderBy(
|
||||
Session::get('tickets.sort_col'),
|
||||
Session::get('tickets.sort_way')
|
||||
);
|
||||
} else {
|
||||
$tickets = $tickets->orderBy('id', 'DESC');
|
||||
$tickets->orderBy('id', 'DESC');
|
||||
}
|
||||
return $tickets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search ticket sql request constructor
|
||||
*
|
||||
* @param Builder $tickets
|
||||
* @param Request $request
|
||||
* @return Builder
|
||||
*/
|
||||
private function searchTicket(Builder $tickets, Request $request): Builder
|
||||
{
|
||||
$words = explode(' ', $request->search);
|
||||
return $tickets->where(function ($q) use ($words) {
|
||||
foreach ($words as $word) {
|
||||
if (!strlen($word)) {
|
||||
continue;
|
||||
}
|
||||
$q = $q->where(function ($q) use ($word) {
|
||||
if ($request->exists('search')) {
|
||||
if (empty($request->search)) {
|
||||
Session::remove('tickets.search');
|
||||
} else {
|
||||
Session::put('tickets.search', $request->search);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Session::has('tickets.search')) {
|
||||
return $tickets;
|
||||
}
|
||||
|
||||
return $this->searchQuery($tickets, Session::get('tickets.search'), function ($q) {
|
||||
$words = explode(' ', Session::get('tickets.search'));
|
||||
if (\is_array($words)) {
|
||||
foreach ($words as $word) {
|
||||
if (!strlen($word)) {
|
||||
continue;
|
||||
}
|
||||
$s = collect(TicketStatus::toArray())->filter(function (TicketStatus $t) use ($word) {
|
||||
return strpos(strtolower($t->label), $word) !== false;
|
||||
return Str::slug($t->label) === Str::slug($word);
|
||||
})->map(function (TicketStatus $ticket) {
|
||||
return $ticket->value;
|
||||
});
|
||||
$t = collect(TicketType::toArray())->filter(function (TicketType $t) use ($word) {
|
||||
return strpos(strtolower($t->label), $word) !== false;
|
||||
$t = collect(TicketType::toArray())->filter(function (TicketType $s) use ($word) {
|
||||
return Str::slug($s->label) === Str::slug($word);
|
||||
})->map(function (TicketType $ticket) {
|
||||
return $ticket->value;
|
||||
});
|
||||
$q->where('id', $word)
|
||||
->orwhere('text', 'LIKE', "%$word%")
|
||||
$q->orWhere('id', $word)
|
||||
->orWhereIn('status', $s)
|
||||
->orWhereIn('type', $t)
|
||||
->orWhere('file', 'LIKE', "%$word%")
|
||||
->orWhere('file2', 'LIKE', "%$word%")
|
||||
->orWhereHas('user', function ($q) use ($word) {
|
||||
$q->where('name', 'LIKE', "%$word%");
|
||||
});
|
||||
});
|
||||
}//end foreach
|
||||
});
|
||||
}
|
||||
|
||||
public function show(Request $request, Ticket $ticket)
|
||||
{
|
||||
if ($request->ajax()) {
|
||||
return \response()->json([
|
||||
'label' => sprintf(
|
||||
'Ticket %d (%s): %s',
|
||||
$ticket->id,
|
||||
$ticket->user ? $ticket->user->email : __('Utilisateur supprimé'),
|
||||
Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35)
|
||||
),
|
||||
'value' => $ticket->id
|
||||
]);
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user->can('update', $ticket)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
if ($ticket->is_internal === 1 && !$user->can('changeInternalStatus', $ticket)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$messages = $ticket->messages()->orderBy('created_at', 'DESC')->get();
|
||||
$isSubscribed = (bool)$ticket->subscriptions()->where('user_id', $user->id)->count();
|
||||
return view('ticket.show', compact('ticket', 'messages', 'isSubscribed'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
if ($user->isAdmin()) {
|
||||
$sites = Site::orderBy('name')->get();
|
||||
} else {
|
||||
$sites = $user->sites;
|
||||
}
|
||||
|
||||
if (!count($sites)) {
|
||||
return redirect()->route('sites.create')
|
||||
->with('warning', 'Vous devez tout d\'abord créer un site pour y ajouter un ticket');
|
||||
}
|
||||
return view('ticket.create', compact('sites'));
|
||||
}
|
||||
|
||||
public function store(StoreTicket $request)
|
||||
{
|
||||
$site = Site::findOrFail($request->site_id);
|
||||
|
||||
$ticket = new Ticket($request->validated());
|
||||
|
||||
if ($request->file_ticket) {
|
||||
$ticket->file = Ticket::saveFile($site, $ticket, $ticket->type, $request->file_ticket);
|
||||
}
|
||||
if ($request->file_ticket2) {
|
||||
$ticket->file2 = Ticket::saveFile($site, $ticket, $ticket->type, $request->file_ticket2);
|
||||
}
|
||||
|
||||
if ($request->has('is_internal')) {
|
||||
$ticket->is_internal = 1;
|
||||
} else {
|
||||
$ticket->is_internal = 0;
|
||||
}
|
||||
|
||||
$ticket->user()->associate(auth()->user());
|
||||
$ticket->saveOrFail();
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
$user->subscriptions()->toggle($ticket->id);
|
||||
|
||||
if (config('mail.MAIL_ADMIN')) {
|
||||
Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket));
|
||||
} else {
|
||||
throw new Exception('Missing MAIL_ADMIN in .env');
|
||||
}
|
||||
|
||||
return redirect()->route('ticket.show', $ticket)
|
||||
->with('success', 'Ticket enregistré !');
|
||||
}
|
||||
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
public function edit(Ticket $ticket)
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
if ($user->isAdmin()) {
|
||||
$sites = Site::orderBy('name')->get();
|
||||
} else {
|
||||
$sites = $user->sites;
|
||||
}
|
||||
return view('ticket.edit', compact('ticket', 'sites'));
|
||||
}
|
||||
|
||||
public function update(UpdateTicket $request, Ticket $ticket)
|
||||
{
|
||||
$site = Site::findOrFail($request->site_id);
|
||||
$ticket->fill($request->validated());
|
||||
|
||||
if ($request->file_ticket) {
|
||||
if (File::exists($ticket->file)) {
|
||||
File::delete($ticket->file);
|
||||
}
|
||||
$ticket->file = Ticket::saveFile($site, $ticket, $ticket->type, $request->file_ticket);
|
||||
}
|
||||
if ($request->file_ticket2) {
|
||||
if (File::exists($ticket->file2)) {
|
||||
File::delete($ticket->file2);
|
||||
}
|
||||
$ticket->file2 = Ticket::saveFile($site, $ticket, $ticket->type, $request->file_ticket2);
|
||||
}
|
||||
|
||||
if ($request->has('is_internal')) {
|
||||
$ticket->is_internal = 1;
|
||||
} else {
|
||||
$ticket->is_internal = 0;
|
||||
}
|
||||
|
||||
$ticket->save();
|
||||
|
||||
return redirect()->route('ticket.edit', $ticket->id)
|
||||
->with('success', 'Modifications enregistrées !');
|
||||
}
|
||||
|
||||
public function changeStatus(ChangeTicketStatus $request, Ticket $ticket)
|
||||
{
|
||||
$ticket->fill($request->validated());
|
||||
$ticket->save();
|
||||
|
||||
return back()
|
||||
->with('warning', trans('Le status à été modifié'));
|
||||
}
|
||||
|
||||
public function createMessage(CreateMessage $request, Ticket $ticket)
|
||||
{
|
||||
$fileMessage = $request->file_message;
|
||||
|
||||
$message = new Message($request->validated());
|
||||
$message->user()->associate(auth()->user());
|
||||
$message->ticket()->associate($ticket);
|
||||
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
|
||||
// Toggle auth user subscription
|
||||
if ($request->subscribe) {
|
||||
if (!$user->subscriptions()->find($ticket->id)) {
|
||||
$user->subscriptions()->attach($ticket->id);
|
||||
}
|
||||
} elseif ($user->subscriptions()->find($ticket->id)) {
|
||||
$user->subscriptions()->detach($ticket->id);
|
||||
}
|
||||
|
||||
$message->saveOrFail();
|
||||
|
||||
// ici $message->id n'est pas connu lors de la création du message.
|
||||
// on doit donc sauvegarder cette info après le create initial
|
||||
if ($request->file_message) {
|
||||
$siteSlug = Str::slug($ticket->site->name);
|
||||
$fileMessage = $request->file_message;
|
||||
$fileName = $siteSlug . '-ticket-' . $ticket->id .
|
||||
'-message-' . $message->id . '.' . $fileMessage->extension();
|
||||
$fileMessage->move(public_path('fichiers/' . $siteSlug . '/' . $ticket->id . '/'), $fileName);
|
||||
$filePath = 'fichiers/' . $siteSlug . '/' . $ticket->id . '/' . $fileName;
|
||||
$message->file = $filePath;
|
||||
$message->saveOrFail();
|
||||
}
|
||||
|
||||
|
||||
return back()
|
||||
->with('success', trans('Votre message à été ajouté'));
|
||||
}
|
||||
|
||||
public function updateMessage(UpdateMessage $request, Ticket $ticket, Message $message)
|
||||
{
|
||||
$message->message = $request->message;
|
||||
$message->saveOrFail();
|
||||
|
||||
return back()
|
||||
->with('success', trans('Votre message à bien été modifié'));
|
||||
}
|
||||
|
||||
public function setToArchive(Ticket $ticket)
|
||||
{
|
||||
$ticket->status = TicketStatus::archived();
|
||||
$ticket->saveOrFail();
|
||||
return redirect()->route('ticket.index')
|
||||
->with('warning', trans('Le ticket est archivé'));
|
||||
}
|
||||
|
||||
public function subscribe(Ticket $ticket)
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
User::find(Auth::user()->id)->subscriptions()->toggle($ticket->id);
|
||||
$status = $user->subscriptions()->where('ticket_id', $ticket->id)->exists();
|
||||
if ($status) {
|
||||
return back()
|
||||
->with('success', trans('Vous avez été inscrit'));
|
||||
}
|
||||
return back()->with('warning', trans('Vous avez été désinscrit'));
|
||||
}
|
||||
|
||||
public function reopen(Ticket $ticket)
|
||||
{
|
||||
$message = "{$ticket->user->name} à réouvert le ticket n°{$ticket->id}";
|
||||
$oldStatus = $ticket->status;
|
||||
$ticket->status = TicketStatus::inprogress();
|
||||
$ticket->saveQuietly();
|
||||
event(new TicketStatusChanged($ticket, $oldStatus, null, $message));
|
||||
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
|
||||
User::find(Auth::user()->id)->subscriptions()->toggle($ticket->id);
|
||||
return back()->with('warning', trans('Votre ticket à été réouvert.'));
|
||||
} //end foreach
|
||||
} //end if
|
||||
}, 'text', 'file', 'file2');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Kernel extends HttpKernel
|
|||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
\App\Http\Middleware\StartSession::class,
|
||||
\App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Fruitcake\Cors\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
|
|
@ -32,15 +32,13 @@ class Kernel extends HttpKernel
|
|||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\App\Http\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
\App\Http\Middleware\SecureHeaders::class,
|
||||
'xss'
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -61,5 +59,6 @@ class Kernel extends HttpKernel
|
|||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'xss' => \App\Http\Middleware\XssSanitizer::class,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@ class Authenticate extends Middleware
|
|||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param mixed $request
|
||||
* @return string|null
|
||||
*/
|
||||
protected function redirectTo($request)
|
||||
{
|
||||
/** @var \Illuminate\Http\Request $request */
|
||||
if (! $request->expectsJson()) {
|
||||
return route('login');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,5 @@ class PreventRequestsDuringMaintenance extends Middleware
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
];
|
||||
protected $except = [];
|
||||
}
|
||||
|
|
|
|||
71
app/Http/Middleware/SecureHeaders.php
Normal file
71
app/Http/Middleware/SecureHeaders.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class SecureHeaders
|
||||
{
|
||||
/**
|
||||
* Enumerate headers which you do not want in your application's responses.
|
||||
* Great starting point would be to go check out @Scott_Helme's:
|
||||
* https://securityheaders.com/
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $unwantedHeaderList = [
|
||||
'X-Powered-By',
|
||||
'Server',
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
View::share('nonce', $nonce = \uniqid());
|
||||
// * For VueJS dev tool to execute
|
||||
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-$nonce'";
|
||||
$reportUri = Route::has('cspReportUri') ?
|
||||
\sprintf('report-uri %s', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
|
||||
$this->removeUnwantedHeaders($this->unwantedHeaderList);
|
||||
$response = $next($request);
|
||||
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||
$response->headers->set('X-XSS-Protection', '1; mode=block');
|
||||
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
|
||||
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains;');
|
||||
$cspPolicies = [
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;",
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
"script-src 'self' $jsDev https://cdnjs.cloudflare.com;"
|
||||
];
|
||||
$response->headers->set(
|
||||
'Content-Security-Policy',
|
||||
implode(' ', $cspPolicies) . $reportUri
|
||||
);
|
||||
$response->headers->set('Feature-Policy', 'fullscreen \'self\';');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unwanted headers
|
||||
*
|
||||
* @param array $headerList
|
||||
* @return void
|
||||
*/
|
||||
private function removeUnwantedHeaders(array $headerList)
|
||||
{
|
||||
foreach ($headerList as $header) {
|
||||
header_remove($header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,11 +9,12 @@ class StartSession extends MiddlewareStartSession
|
|||
/**
|
||||
* Save the session data to storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param mixed $request
|
||||
* @return void
|
||||
*/
|
||||
protected function saveSession($request)
|
||||
{
|
||||
/** @var \Illuminate\Http\Request $request */
|
||||
if (!$request->is('/ping')) {
|
||||
parent::saveSession($request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class TrimStrings extends Middleware
|
|||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'password',
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class TrustHosts extends Middleware
|
|||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* @return array
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
public function hosts()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,5 +19,10 @@ class TrustProxies extends Middleware
|
|||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $headers = Request::HEADER_X_FORWARDED_ALL;
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ class VerifyCsrfToken extends Middleware
|
|||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
];
|
||||
protected $except = [];
|
||||
}
|
||||
|
|
|
|||
46
app/Http/Middleware/XssSanitizer.php
Normal file
46
app/Http/Middleware/XssSanitizer.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class XssSanitizer
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param mixed ...$params
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next, ...$params)
|
||||
{
|
||||
$inputs = $request->all();
|
||||
array_walk_recursive($inputs, function (&$input, $name) use ($params) {
|
||||
if (!is_string($input)) {
|
||||
return;
|
||||
}
|
||||
if (in_array($name, $params)) {
|
||||
// ? Is a json input ?
|
||||
if ($isJson = \json_decode($input, true) and is_array($isJson)) {
|
||||
// ! Decode each json field recursive.
|
||||
\array_walk_recursive($isJson, function (&$item) {
|
||||
if (\is_string($item)) {
|
||||
$item = clean($item);
|
||||
$item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$input = clean($input);
|
||||
$input = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($input);
|
||||
}
|
||||
} else {
|
||||
$input = strip_tags($input);
|
||||
}
|
||||
});
|
||||
$request->merge($inputs);
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
42
app/Http/Requests/AssignTicketToUserRequest.php
Normal file
42
app/Http/Requests/AssignTicketToUserRequest.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AssignTicketToUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
return auth()->check() and $user->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'assign' => [
|
||||
'nullable', 'exists:users,id',
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
function ($attribute, $value, $fail) {
|
||||
/** @var \App\Models\User $user */
|
||||
if ($user = User::whereId($value)->first() and !$user->isAdmin()) {
|
||||
$fail(trans('Cet utilisateur ne peut se voir assigner un ticket'));
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -13,13 +13,28 @@ class CreateMessage extends FormRequest
|
|||
*/
|
||||
public function authorize()
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
/** @var \App\Models\User|null */
|
||||
if (!($user = auth()->user())) {
|
||||
return false;
|
||||
}
|
||||
return auth()->check() and auth()->user()->sites and (
|
||||
$user->isAdmin() or
|
||||
auth()->user()->sites->contains(request()->route()->ticket->site_id)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
|
||||
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'subscribe' => $this->subscribe ? true : false
|
||||
]);
|
||||
return auth()->check() and ($user->isAdmin() or
|
||||
auth()->user()->sites->contains(request()->route()->ticket->site_id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
41
app/Http/Requests/Credit/AddCreditToAccountRequest.php
Normal file
41
app/Http/Requests/Credit/AddCreditToAccountRequest.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AddCreditToAccountRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
if (!auth()->check()) {
|
||||
return false;
|
||||
}
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
return $user->isAdmin() and $user->can('credit', $this->account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'amount' => [
|
||||
'required', 'numeric', 'gt:0', function ($attribute, $value, $fail) {
|
||||
if ((\floatval($value) * 10) % 5 !== 0) {
|
||||
$fail("$attribute doit être un multiple de 5 (comptez par demi journées, soit 0.5)");
|
||||
}
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
48
app/Http/Requests/Credit/AnswerEstimateRequest.php
Normal file
48
app/Http/Requests/Credit/AnswerEstimateRequest.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AnswerEstimateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
if (!auth()->check()) {
|
||||
return false;
|
||||
}
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
return !$user->isAdmin() and $user->can('validateEstimate', $this->ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'answer' => $this->status === 'accept'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'status' => 'required|in:accept,refuse',
|
||||
'answer' => 'required|boolean'
|
||||
];
|
||||
}
|
||||
}
|
||||
67
app/Http/Requests/Credit/CreateEstimateRequest.php
Normal file
67
app/Http/Requests/Credit/CreateEstimateRequest.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use App\Models\Credit\Account;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateEstimateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
if (!auth()->check()) {
|
||||
return false;
|
||||
}
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
return $user->isAdmin() and $user->can('estimate', $this->ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
/** @var \App\Models\Ticket */
|
||||
$ticket = $this->ticket;
|
||||
return [
|
||||
'amount' => [
|
||||
'required', 'numeric', 'gt:0', function ($attribute, $value, $fail) {
|
||||
if ((\floatval($value) * 10) % 5 !== 0) {
|
||||
$fail("$attribute doit être un multiple de 5 (comptez par demi journées, soit 0.5)");
|
||||
}
|
||||
}
|
||||
],
|
||||
'credit_account_id' => [
|
||||
'required', 'exists:credit_accounts,id',
|
||||
function ($attribute, $value, $fail) use ($ticket) {
|
||||
/** @var \App\Models\Credit\Account|null */
|
||||
$account = Account::whereId($value)->first();
|
||||
if ($account and !$ticket->accountIsValidForThisTicket($account)) {
|
||||
$fail("$attribute does not belong to the ticket brand");
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'amount' => 'Montant',
|
||||
'credit_account_id' => 'Compte'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,12 @@ class PostActivityRequest extends FormRequest
|
|||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->check() and $this->ajax();
|
||||
if (!auth()->check() or !$this->ajax()) {
|
||||
return false;
|
||||
}
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
return $user->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Http\Requests;
|
|||
|
||||
use App\Enums\TicketStatus;
|
||||
use App\Enums\TicketType;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreTicket extends FormRequest
|
||||
|
|
@ -15,16 +16,34 @@ class StoreTicket extends FormRequest
|
|||
*/
|
||||
public function authorize()
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
if (!$user->isAdmin()) {
|
||||
$this->merge([
|
||||
'status' => TicketStatus::new()->value,
|
||||
]);
|
||||
}
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
/** @var \App\Models\User|null */
|
||||
$user = auth()->user();
|
||||
if ($user) {
|
||||
if (!$user->isAdmin()) {
|
||||
$this->merge([
|
||||
'status' => TicketStatus::new()->value,
|
||||
'is_internal' => false,
|
||||
]);
|
||||
} else {
|
||||
$this->merge([
|
||||
'is_internal' => $this->is_internal ? true : false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
|
|
@ -32,6 +51,8 @@ class StoreTicket extends FormRequest
|
|||
*/
|
||||
public function rules()
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
return [
|
||||
'site_id' => [
|
||||
'required',
|
||||
|
|
@ -75,6 +96,16 @@ class StoreTicket extends FormRequest
|
|||
'text' => 'required|string|min:40',
|
||||
'file_ticket' => 'mimes:jpeg,jpg,png,pdf|max:2048',
|
||||
'file_ticket2' => 'mimes:jpeg,jpg,png,pdf|max:2048',
|
||||
'is_internal' => 'required|boolean',
|
||||
'labels' => [
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
'sometimes', 'array', function ($attribute, $value, $fail) use ($user) {
|
||||
if (!$user->can('assignLabels', Ticket::class)) {
|
||||
$fail(trans('Vous n\'êtes pas autorisé à modifier cette valeur'));
|
||||
}
|
||||
}
|
||||
],
|
||||
'labels.*' => 'required|exists:labels,id'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,19 @@ namespace App\Http\Requests;
|
|||
|
||||
class UpdateTicket extends StoreTicket
|
||||
{
|
||||
public function authorize()
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
if (!$user->isAdmin()) {
|
||||
$this->merge(['status' => $this->site->status->value,
|
||||
]);
|
||||
/** @var \App\Models\User|null */
|
||||
if (!($user = auth()->user())) {
|
||||
return false;
|
||||
}
|
||||
return auth()->check() and ($user->isAdmin() or
|
||||
request()->route()->ticket->created_by === auth()->user()->id);
|
||||
optional(request()->route()->ticket)->created_by === $user->id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
289
app/Lib/ToolboxHelper.php
Normal file
289
app/Lib/ToolboxHelper.php
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib;
|
||||
|
||||
use App\Enums\LoadLevel;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class ToolboxHelper
|
||||
{
|
||||
/**
|
||||
* Compute plain text to html representation.
|
||||
*
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
public static function plainToHtml(string $text): string
|
||||
{
|
||||
$text = e($text);
|
||||
// * url replace and email replace
|
||||
$text = preg_replace_callback(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/(?P<email>[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,})|(?P<user>@\w*)|(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
|
||||
function ($matches) {
|
||||
$match = collect($matches)->first() ?? '';
|
||||
if (filter_var($match, FILTER_VALIDATE_EMAIL)) {
|
||||
return strlen($match) ? "<a href=\"mailto:$match\">$match</a>" : $match;
|
||||
} elseif (isset($matches['user']) and strlen($matches['user'])) {
|
||||
return sprintf(
|
||||
'<span class="text-primary fw-bold font-weight-bold">%s</span>',
|
||||
$matches['user']
|
||||
);
|
||||
} else {
|
||||
$url = $match;
|
||||
if (\strpos($url, 'http') === false and \strpos($url, 'https') === false) {
|
||||
$url = sprintf(
|
||||
'%s://%s',
|
||||
config('app.env') !== 'local' ? 'https' : 'http',
|
||||
$url
|
||||
);
|
||||
}
|
||||
$fsligthbox = self::isImageFile($url) ? 'data-fslightbox="gallery"' : '';
|
||||
return strlen($match) ? "<a $fsligthbox href=\"$url\" target=\"_blank\">$match</a>" : $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return nl2br($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check uri points to image file
|
||||
*
|
||||
* @param string $url
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isImageFile(string $url): bool
|
||||
{
|
||||
$m = [];
|
||||
return \preg_match('/(\.png|\.jpg|\.jpeg|\.gif)$/', $url, $m) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* delais 7 jours 168h
|
||||
* travaillé 5 jours 37h
|
||||
* temps tickets 1 jours 7.4h
|
||||
*
|
||||
* personnes 4 (TpP) 3.7h / semaine * prs
|
||||
*
|
||||
* délais moyen ticket (Dmt) 0.75h
|
||||
*
|
||||
* délais max attente pour 1 ticket (Dam) 7jours
|
||||
*
|
||||
* niveaux de charge 4: - faible
|
||||
* - stable
|
||||
* - élevée
|
||||
* - saturation
|
||||
*
|
||||
* états tickets(Et) : - nouveaux (non traités)
|
||||
* - en attente (charge pendante)
|
||||
* - traités
|
||||
*
|
||||
* Algo : linéaire
|
||||
*
|
||||
* TpP : 3.7h
|
||||
* P : 4
|
||||
*
|
||||
* Et(nvx) * Dam
|
||||
*
|
||||
* Nv Charge = (P * TpP) / (Et(nvx) * Dam) - (P * TpP) / (Et(nvx) * DmT) - (Et(att) * DmT)
|
||||
*
|
||||
*
|
||||
* Si nv Charge < 0 => saturation
|
||||
*
|
||||
* @return \App\Enums\LoadLevel
|
||||
*/
|
||||
public static function computeWorkload(): LoadLevel
|
||||
{
|
||||
$Tnew = View::shared('ticketsNew');
|
||||
$Tprogress = View::shared('ticketsInProgress');
|
||||
$Twaiting = View::shared('ticketsWaiting');
|
||||
|
||||
$worked = config('tickets.workedHoursInAWeek');
|
||||
$workedDays = config('tickets.workedDaysInAWeek');
|
||||
// * in hours
|
||||
$morningWorkTime = ($worked / $workedDays / 2);
|
||||
// * Devs handlings tickets per week
|
||||
$workingDevsPerWeek = config('tickets.workingDevsPerWeek');
|
||||
// * Worktime per week to handle tickets
|
||||
$timePerWeekDev = $workingDevsPerWeek * $morningWorkTime;
|
||||
// * 45min for a ticket
|
||||
$avgTimeForTicket = config('tickets.avgTimeForTicket');
|
||||
// * The time needed to handle new ticket (average)
|
||||
$todoListDelay = $Tnew * $avgTimeForTicket;
|
||||
// * The time needed to handle new ticket (average)
|
||||
$waitingListDelay = $Twaiting * $avgTimeForTicket;
|
||||
// * The time needed to handle new ticket (average)
|
||||
$progressListDelay = $Tprogress * $avgTimeForTicket;
|
||||
$totalTicketsDelay = $todoListDelay + $progressListDelay + $waitingListDelay;
|
||||
// * in weeks
|
||||
$ETAForAnewTicket = $totalTicketsDelay / $timePerWeekDev;
|
||||
switch ((int)round($ETAForAnewTicket)) {
|
||||
case LoadLevel::weak()->value:
|
||||
case LoadLevel::stable()->value:
|
||||
case LoadLevel::high()->value:
|
||||
return LoadLevel::make((int)round($ETAForAnewTicket));
|
||||
default:
|
||||
return LoadLevel::overload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip scheme (https) on oembed urls
|
||||
* Only embed allowed are for MP4 self hosted videos right now
|
||||
*
|
||||
* https is force by ckeditor MediaElements which natively supports
|
||||
* only cloud hosted medias (youtube, spotify etc..)
|
||||
*
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
|
||||
{
|
||||
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert That no Back Office url is present on txt
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $text
|
||||
* @return string
|
||||
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
|
||||
*/
|
||||
public static function assertNoInternalBoUrls(string $field, string $text): string
|
||||
{
|
||||
$routes = Route::getRoutes();
|
||||
// * Url replace and email replace.
|
||||
$text = preg_replace_callback(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
|
||||
function ($matches) use ($routes, $field) {
|
||||
$match = collect($matches)->first() ?? '';
|
||||
$url = $match;
|
||||
try {
|
||||
$request = Request::create($url);
|
||||
// If a route is match then it do not throws exception.
|
||||
if (
|
||||
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
|
||||
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
|
||||
) {
|
||||
// Removes the matched url.
|
||||
throw ValidationException::withMessages([
|
||||
$field => sprintf(
|
||||
'%s : Il est interdit de coller une url du back office dans ce champ',
|
||||
trans($field)
|
||||
)
|
||||
]);
|
||||
}
|
||||
return $match;
|
||||
} catch (NotFoundHttpException $e) {
|
||||
return $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an image of different types
|
||||
*
|
||||
* @param string $table
|
||||
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $image
|
||||
* @return string
|
||||
* @throws \ErrorException If file copy fails.
|
||||
*/
|
||||
public static function storeImage(string $table, $image): string
|
||||
{
|
||||
/** @var \Illuminate\Http\UploadedFile $image */
|
||||
if (\is_object($image) and get_class($image) === UploadedFile::class) {
|
||||
$newPath = self::prepareMoveFile($table, $image->getClientOriginalName());
|
||||
$newPath = str_replace(storage_path('app'), '', $newPath);
|
||||
$image->storePubliclyAs(
|
||||
pathinfo($newPath, \PATHINFO_DIRNAME),
|
||||
pathinfo($newPath, \PATHINFO_BASENAME)
|
||||
);
|
||||
return 'storage' . str_replace('/public', '', $newPath);
|
||||
}
|
||||
/** @var \SplFileInfo $image */
|
||||
if (\is_object($image) and get_class($image) === \SplFileInfo::class) {
|
||||
$newPath = self::prepareMoveFile($table, $image->getBaseName());
|
||||
if (!File::copy($image->getRealPath(), $newPath)) {
|
||||
throw new \ErrorException(sprintf(
|
||||
'Failed to copy file %s to %s',
|
||||
$image->getRealPath(),
|
||||
$newPath
|
||||
));
|
||||
}
|
||||
return \ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
|
||||
}
|
||||
if (\is_string($image)) {
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare storage path and return
|
||||
* the image full path
|
||||
*
|
||||
* @param string $table Model table name.
|
||||
* @param string $filename The basename for the file.
|
||||
* @return string
|
||||
*/
|
||||
private static function prepareMoveFile(string $table, string $filename): string
|
||||
{
|
||||
$pathFolder = \storage_path(sprintf('app/public/images/%s', $table));
|
||||
if (!File::exists($pathFolder)) {
|
||||
File::makeDirectory($pathFolder, 0755, true);
|
||||
}
|
||||
$filename = sprintf('%s-%s', Str::uuid(), $filename);
|
||||
$fullPath = sprintf('%s/%s', $pathFolder, $filename);
|
||||
if (File::exists($fullPath)) {
|
||||
File::delete($fullPath);
|
||||
}
|
||||
return $fullPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert Is Unique
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param array<string> $columns
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If a model like this already exists.
|
||||
*/
|
||||
public static function assertIsUnique(Model $model, array $columns): void
|
||||
{
|
||||
$columns = collect($columns);
|
||||
/** @var string|null */
|
||||
$firstColumn = $columns->pull($columns->keys()->first());
|
||||
if (!$firstColumn) {
|
||||
return;
|
||||
}
|
||||
$modelClass = \get_class($model);
|
||||
$uniqueRule = Rule::unique((new $modelClass())->getTable());
|
||||
if ($model->id) {
|
||||
$uniqueRule->ignore($model->id);
|
||||
}
|
||||
$uniqueRule->where(function ($q) use ($model, $columns) {
|
||||
$columns->map(function (string $column) use ($model, $q) {
|
||||
$q->where($column, $model->{$column});
|
||||
});
|
||||
});
|
||||
Validator::make([$firstColumn => $model->{$firstColumn}], [
|
||||
$firstColumn => ['required', $uniqueRule]
|
||||
])->validate();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib;
|
||||
|
||||
use App\Enums\LoadLevel;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class Utils
|
||||
{
|
||||
/**
|
||||
* Compute plain text to html representation.
|
||||
*
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
public static function plainToHtml(string $text): string
|
||||
{
|
||||
$text = e($text);
|
||||
// * url replace and email replace
|
||||
$text = preg_replace_callback(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/(?P<email>[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,})|(?P<user>@\w*)|(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
|
||||
function ($matches) {
|
||||
$match = collect($matches)->first() ?? '';
|
||||
if (filter_var($match, FILTER_VALIDATE_EMAIL)) {
|
||||
return strlen($match) ? "<a href=\"mailto:$match\">$match</a>" : $match;
|
||||
} elseif (isset($matches['user']) and strlen($matches['user'])) {
|
||||
return sprintf(
|
||||
'<span class="text-primary fw-bold font-weight-bold">%s</span>',
|
||||
$matches['user']
|
||||
);
|
||||
} else {
|
||||
$url = $match;
|
||||
if (\strpos($url, 'http') === false and \strpos($url, 'https') === false) {
|
||||
$url = sprintf(
|
||||
'%s://%s',
|
||||
config('app.env') !== 'local' ? 'https' : 'http',
|
||||
$url
|
||||
);
|
||||
}
|
||||
$fsligthbox = self::isImageFile($url) ? 'data-fslightbox="gallery"' : '';
|
||||
return strlen($match) ? "<a $fsligthbox href=\"$url\" target=\"_blank\">$match</a>" : $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return nl2br($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check uri points to image file
|
||||
*
|
||||
* @param string $url
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isImageFile(string $url): bool
|
||||
{
|
||||
$m = [];
|
||||
return \preg_match('/(\.png|\.jpg|\.jpeg|\.gif)$/', $url, $m) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* delais 7 jours 168h
|
||||
* travaillé 5 jours 37h
|
||||
* temps tickets 1 jours 7.4h
|
||||
*
|
||||
* personnes 4 (TpP) 3.7h / semaine * prs
|
||||
*
|
||||
* délais moyen ticket (Dmt) 0.75h
|
||||
*
|
||||
* délais max attente pour 1 ticket (Dam) 7jours
|
||||
*
|
||||
* niveaux de charge 4: - faible
|
||||
* - stable
|
||||
* - élevée
|
||||
* - saturation
|
||||
*
|
||||
* états tickets(Et) : - nouveaux (non traités)
|
||||
* - en attente (charge pendante)
|
||||
* - traités
|
||||
*
|
||||
* Algo : linéaire
|
||||
*
|
||||
* TpP : 3.7h
|
||||
* P : 4
|
||||
*
|
||||
* Et(nvx) * Dam
|
||||
*
|
||||
* Nv Charge = (P * TpP) / (Et(nvx) * Dam) - (P * TpP) / (Et(nvx) * DmT) - (Et(att) * DmT)
|
||||
*
|
||||
*
|
||||
* Si nv Charge < 0 => saturation
|
||||
*
|
||||
* @return \App\Enums\LoadLevel
|
||||
*/
|
||||
public static function computeWorkload(): LoadLevel
|
||||
{
|
||||
$Tnew = View::shared('ticketsNew');
|
||||
$Tprogress = View::shared('ticketsInProgress');
|
||||
$Twaiting = View::shared('ticketsWaiting');
|
||||
|
||||
$worked = config('tickets.workedHoursInAWeek');
|
||||
$workedDays = config('tickets.workedDaysInAWeek');
|
||||
// * in hours
|
||||
$morningWorkTime = ($worked / $workedDays / 2);
|
||||
// * Devs handlings tickets per week
|
||||
$workingDevsPerWeek = config('tickets.workingDevsPerWeek');
|
||||
// * Worktime per week to handle tickets
|
||||
$timePerWeekDev = $workingDevsPerWeek * $morningWorkTime;
|
||||
// * 45min for a ticket
|
||||
$avgTimeForTicket = config('tickets.avgTimeForTicket');
|
||||
// * The time needed to handle new ticket (average)
|
||||
$todoListDelay = $Tnew * $avgTimeForTicket;
|
||||
// * The time needed to handle new ticket (average)
|
||||
$waitingListDelay = $Twaiting * $avgTimeForTicket;
|
||||
// * The time needed to handle new ticket (average)
|
||||
$progressListDelay = $Tprogress * $avgTimeForTicket;
|
||||
$totalTicketsDelay = $todoListDelay + $progressListDelay + $waitingListDelay;
|
||||
// * in weeks
|
||||
$ETAForAnewTicket = $totalTicketsDelay / $timePerWeekDev;
|
||||
switch ((int)round($ETAForAnewTicket)) {
|
||||
case LoadLevel::weak()->value:
|
||||
case LoadLevel::stable()->value:
|
||||
case LoadLevel::high()->value:
|
||||
return LoadLevel::make((int)round($ETAForAnewTicket));
|
||||
default:
|
||||
return LoadLevel::overload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Lib\Utils;
|
||||
use App\Lib\ToolboxHelper;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
|
@ -38,7 +38,7 @@ class TicketMention extends Mailable
|
|||
public function build()
|
||||
{
|
||||
$ticket = $this->ticket;
|
||||
$corpsMaj = Utils::plainToHtml($this->corpsMaj);
|
||||
$corpsMaj = ToolboxHelper::plainToHtml($this->corpsMaj);
|
||||
return $this->subject("[SupportWeb] Vous avez été mentionné sur le ticket n° $ticket->id")
|
||||
->view('emails.ticket-mention', compact('ticket', 'corpsMaj'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Lib\Utils;
|
||||
use App\Lib\ToolboxHelper;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
|
@ -38,7 +38,7 @@ class TicketUpdate extends Mailable
|
|||
public function build()
|
||||
{
|
||||
$ticket = $this->ticket;
|
||||
$corpsMaj = \html_entity_decode(Utils::plainToHtml($this->corpsMaj));
|
||||
$corpsMaj = \html_entity_decode(ToolboxHelper::plainToHtml($this->corpsMaj));
|
||||
return $this->subject("[SupportWeb][{$ticket->site->name}] Ticket n°$ticket->id")
|
||||
->view('emails.ticket-update', compact('ticket', 'corpsMaj'));
|
||||
}
|
||||
|
|
|
|||
115
app/Models/Brand.php
Normal file
115
app/Models/Brand.php
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Credit\Account;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudType;
|
||||
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
|
||||
|
||||
class Brand extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use IsCrudModel;
|
||||
|
||||
/**
|
||||
* Crud Label
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudLabelColumn = 'name';
|
||||
|
||||
/**
|
||||
* Crud Value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudValueColumn = 'id';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name'
|
||||
];
|
||||
|
||||
/**
|
||||
* User
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
self::$editableProperties = [
|
||||
'name' => [
|
||||
'label' => trans('Nom'),
|
||||
'type' => CrudType::string(),
|
||||
'validate' => [
|
||||
'string',
|
||||
'max:255'
|
||||
],
|
||||
'required' => true,
|
||||
'nullable' => false,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update(),
|
||||
]
|
||||
],
|
||||
'sites' => [
|
||||
'label' => trans('models.classes.sites'),
|
||||
'type' => CrudType::hasMany(),
|
||||
'hasMany' => Site::class,
|
||||
'hasManyLabel' => 'name',
|
||||
'nullable' => true,
|
||||
'readonly' => false,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update()
|
||||
]
|
||||
],
|
||||
'accounts' => [
|
||||
'label' => trans('models.classes.accounts'),
|
||||
'type' => CrudType::hasMany(),
|
||||
'hasMany' => Account::class,
|
||||
'hasManyLabel' => 'label',
|
||||
'nullable' => true,
|
||||
'readonly' => false,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::view()
|
||||
]
|
||||
],
|
||||
];
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* HasMany Site relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function sites()
|
||||
{
|
||||
return $this->hasMany(Site::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* HasMany Account relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function accounts()
|
||||
{
|
||||
return $this->hasMany(Account::class);
|
||||
}
|
||||
}
|
||||
144
app/Models/Credit/Account.php
Normal file
144
app/Models/Credit/Account.php
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Credit;
|
||||
|
||||
use App\Enums\Credit\AccountType;
|
||||
use App\Models\Brand;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudType;
|
||||
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
|
||||
use Kwaadpepper\Enum\Traits\CastsEnums;
|
||||
|
||||
class Account extends Model
|
||||
{
|
||||
use CastsEnums;
|
||||
use IsCrudModel;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'credit_accounts';
|
||||
|
||||
/**
|
||||
* Crud Label
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudLabelColumn = 'label';
|
||||
|
||||
/**
|
||||
* Crud Value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudValueColumn = 'id';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'label', 'type',
|
||||
'balance', 'brand_id'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to Enums.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enumCasts = [
|
||||
'type' => AccountType::class
|
||||
];
|
||||
|
||||
/**
|
||||
* Account
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
self::$editableProperties = [
|
||||
'label' => [
|
||||
'label' => trans('Label'),
|
||||
'placeholder' => trans('Nom du compte'),
|
||||
'type' => CrudType::string(),
|
||||
'validate' => [
|
||||
'string',
|
||||
'max:255'
|
||||
],
|
||||
'required' => true,
|
||||
'nullable' => false,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update(),
|
||||
]
|
||||
],
|
||||
'type' => [
|
||||
'label' => trans('Type'),
|
||||
'type' => CrudType::enum(),
|
||||
'enum' => AccountType::class,
|
||||
'required' => true,
|
||||
'nullable' => false,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update(),
|
||||
]
|
||||
],
|
||||
'balance' => [
|
||||
'label' => trans('Solde'),
|
||||
'type' => CrudType::float(),
|
||||
'required' => false,
|
||||
'nullable' => false,
|
||||
'readonly' => true,
|
||||
'step' => 0.5,
|
||||
'validate' => [
|
||||
'numeric',
|
||||
function ($attribute, $value, $fail) {
|
||||
if (($value * 10) % 5 !== 0) {
|
||||
$fail("$attribute doit être divisible par 0.5 jour/homme");
|
||||
}
|
||||
},
|
||||
],
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update(),
|
||||
]
|
||||
],
|
||||
'brand' => [
|
||||
'label' => Str::singular(trans('models.classes.brands')),
|
||||
'type' => CrudType::belongsTo(),
|
||||
'belongsTo' => Brand::class,
|
||||
'required' => false,
|
||||
'nullable' => false,
|
||||
'actions' => [
|
||||
CrudAction::viewAny()
|
||||
]
|
||||
]
|
||||
];
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* BelongsTo Brand relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function brand()
|
||||
{
|
||||
return $this->belongsTo(Brand::class);
|
||||
}
|
||||
}
|
||||
122
app/Models/Credit/CreditLog.php
Normal file
122
app/Models/Credit/CreditLog.php
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Credit;
|
||||
|
||||
use App\Enums\Credit\BalanceOperation;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudType;
|
||||
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
|
||||
use Kwaadpepper\Enum\Traits\CastsEnums;
|
||||
|
||||
class CreditLog extends Model
|
||||
{
|
||||
use CastsEnums;
|
||||
use IsCrudModel;
|
||||
|
||||
/**
|
||||
* Crud Label
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudLabelColumn = 'type';
|
||||
|
||||
/**
|
||||
* Crud Value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudValueColumn = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to Enums.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enumCasts = [
|
||||
'type' => BalanceOperation::class
|
||||
];
|
||||
|
||||
/**
|
||||
* Account
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
self::$editableProperties = [
|
||||
'amount' => [
|
||||
'label' => trans('Montant'),
|
||||
'type' => CrudType::float(),
|
||||
'actions' => [
|
||||
CrudAction::viewAny()
|
||||
]
|
||||
],
|
||||
'type' => [
|
||||
'label' => trans('Opération'),
|
||||
'type' => CrudType::enum(),
|
||||
'enum' => BalanceOperation::class,
|
||||
'actions' => [
|
||||
CrudAction::viewAny()
|
||||
]
|
||||
],
|
||||
'created_at' => [
|
||||
'label' => trans('Date'),
|
||||
'type' => CrudType::datetime(),
|
||||
'actions' => [
|
||||
CrudAction::viewAny()
|
||||
]
|
||||
]
|
||||
];
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
$model->created_at = now();
|
||||
if ($model->type->equals(BalanceOperation::debit())) {
|
||||
$model->account->balance = $model->account->balance - $model->amount;
|
||||
}
|
||||
if ($model->type->equals(BalanceOperation::credit())) {
|
||||
$model->account->balance = $model->account->balance + $model->amount;
|
||||
}
|
||||
$model->account->saveOrFail();
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* BelongsToMany estimation relation (1 nullable)
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function estimation()
|
||||
{
|
||||
return $this->belongsToMany(Estimation::class, EstimationLog::class, 'credit_log_id', 'credit_estimation_id')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo account relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class, 'credit_account_id');
|
||||
}
|
||||
}
|
||||
108
app/Models/Credit/Estimation.php
Normal file
108
app/Models/Credit/Estimation.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Credit;
|
||||
|
||||
use App\Enums\Credit\BalanceOperation;
|
||||
use App\Enums\Credit\EstimationStatus;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Kwaadpepper\Enum\Traits\CastsEnums;
|
||||
|
||||
class Estimation extends Model
|
||||
{
|
||||
use CastsEnums;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to Enums.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enumCasts = [
|
||||
'status' => EstimationStatus::class
|
||||
];
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'credit_estimations';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'amount', 'status', 'credit_account_id'
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::updating(function (self $model) {
|
||||
if (!$model->original['validator_id'] !== $model->validator_id) {
|
||||
$log = new CreditLog();
|
||||
$log->account()->associate($model->account);
|
||||
$log->type = BalanceOperation::debit();
|
||||
$log->amount = $model->amount;
|
||||
$log->saveOrFail();
|
||||
$log->estimation()->sync([$model->id]);
|
||||
}
|
||||
if ($model->status->equals(EstimationStatus::pending())) {
|
||||
throw ValidationException::withMessages([trans('Impossible de modifier une estimation')]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* BelongsTo Ticket relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function ticket()
|
||||
{
|
||||
return $this->belongsTo(Ticket::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo Credit\Account relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class, 'credit_account_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo User relation
|
||||
* The use who created the estimate
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function estimator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'estimator_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo User relation (nullable)
|
||||
* The use who validated the estimate
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function validator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'validator_id');
|
||||
}
|
||||
}
|
||||
15
app/Models/Credit/EstimationLog.php
Normal file
15
app/Models/Credit/EstimationLog.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Credit;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
class EstimationLog extends Pivot
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'credit_estimation_log';
|
||||
}
|
||||
136
app/Models/Label.php
Normal file
136
app/Models/Label.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\HtmlColor;
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudType;
|
||||
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
|
||||
use LVR\Colour\Hex;
|
||||
|
||||
class Label extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use IsCrudModel;
|
||||
|
||||
/**
|
||||
* Crud Label
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudLabelColumn = 'name';
|
||||
|
||||
/**
|
||||
* Crud Value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudValueColumn = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'color' => HtmlColor::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'color'
|
||||
];
|
||||
|
||||
/**
|
||||
* Site
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
self::$editableProperties = [
|
||||
'name' => [
|
||||
'name' => trans('Libellé'),
|
||||
'type' => CrudType::string(),
|
||||
'validate' => [
|
||||
'required',
|
||||
'min:2',
|
||||
'max:255',
|
||||
(new Unique('labels', 'name'))->ignore(request()->label)
|
||||
],
|
||||
'required' => true,
|
||||
'nullable' => false,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update(),
|
||||
]
|
||||
],
|
||||
'color' => [
|
||||
'label' => trans('Couleur'),
|
||||
'type' => CrudType::color(),
|
||||
'validate' => ['required', new Hex()],
|
||||
'required' => true,
|
||||
'nullable' => false,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update(),
|
||||
]
|
||||
]
|
||||
];
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
ToolboxHelper::assertIsUnique($model, ['name']);
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
ToolboxHelper::assertIsUnique($model, ['name']);
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* BelongsToMany Ticket relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function tickets()
|
||||
{
|
||||
return $this->belongsToMany(Ticket::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo Site relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function site()
|
||||
{
|
||||
return $this->belongsTo(Site::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,16 +6,25 @@ use App\Events\NotifySubscriptions;
|
|||
use App\Events\NotifyTicketCreator;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
class Message extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'message'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function (Message $message) {
|
||||
|
|
@ -50,7 +59,7 @@ class Message extends Model
|
|||
// https://supportweb.ldc.fr/ticket/583/message/create
|
||||
// SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '583-8' for key 'PRIMARY'
|
||||
// $ticket->subscriptions()->attach($messageUser);
|
||||
// Add subscription
|
||||
// Add subscription.
|
||||
if (!$messageUser->subscriptions()->find($ticket->id)) {
|
||||
$messageUser->subscriptions()->attach($ticket->id);
|
||||
\session()->flash('info', trans('Vous avez été inscrit.'));
|
||||
|
|
@ -69,28 +78,25 @@ class Message extends Model
|
|||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* BelongsTo Ticket relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function ticket()
|
||||
{
|
||||
return $this->belongsTo(Ticket::class, 'ticket_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo User relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function saveFile(Site $site, Ticket $ticket, UploadedFile $file)
|
||||
{
|
||||
$siteAssetFullPath = Site::ticketAssetsFullPath($site->slug_name);
|
||||
$fileAssetPath = Site::ticketAssetsPath($site->slug_name);
|
||||
$fileName = sprintf(
|
||||
'%s-%s-%d-%d.%s',
|
||||
$site->slug_name,
|
||||
$ticket->id,
|
||||
rand(),
|
||||
$file->extension()
|
||||
);
|
||||
$file->move(sprintf('%s/messages/%d', $siteAssetFullPath, $ticket->id), $fileName);
|
||||
return sprintf('%s/%d/%s', $fileAssetPath, $ticket->id, $fileName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ class Project extends Model
|
|||
CrudAction::viewAny(),
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update()
|
||||
]
|
||||
],
|
||||
'markers' => [
|
||||
|
|
|
|||
|
|
@ -18,20 +18,45 @@ class Site extends Model
|
|||
use IsCrudModel;
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* Crud Label
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudLabelColumn = 'name';
|
||||
|
||||
/**
|
||||
* Crud Value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudValueColumn = 'id';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'status',
|
||||
'description', 'todo'
|
||||
];
|
||||
|
||||
/**
|
||||
* The model's attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'status' => '',
|
||||
];
|
||||
|
||||
public function __construct($attributes = [])
|
||||
/**
|
||||
* Site
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
self::$editableProperties = [
|
||||
'name' => [
|
||||
|
|
@ -121,7 +146,7 @@ class Site extends Model
|
|||
]
|
||||
],
|
||||
'projects' => [
|
||||
'label' => trans('Projets'),
|
||||
'label' => trans('models.classes.projects'),
|
||||
'type' => CrudType::belongsToMany(),
|
||||
'belongsToMany' => Project::class,
|
||||
'belongsToManyLabel' => 'name',
|
||||
|
|
@ -142,72 +167,93 @@ class Site extends Model
|
|||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function (Site $site) {
|
||||
$path = Site::ticketAssetsFullPath($site->slug_name);
|
||||
if (!File::exists($path)) {
|
||||
File::makeDirectory($path, 0755, true);
|
||||
}
|
||||
static::created(function (self $site) {
|
||||
Label::createDefaultLabelsFor($site);
|
||||
});
|
||||
static::updating(function (Site $site) {
|
||||
$oldName = Site::where('id', $site->id)->first()->name;
|
||||
$oldPath = Site::ticketAssetsFullPath($oldName);
|
||||
$newPath = Site::ticketAssetsFullPath($site->slug_name);
|
||||
if (File::exists($oldPath)) {
|
||||
File::move($oldPath, $newPath);
|
||||
} elseif (!File::exists($newPath)) {
|
||||
File::makeDirectory($newPath, 0755, true);
|
||||
}
|
||||
});
|
||||
static::updated(function (Site $site) {
|
||||
static::updated(function (self $site) {
|
||||
event(new SiteUpdated($site, \auth()->user()));
|
||||
});
|
||||
static::deleted(function (Site $site) {
|
||||
$path = Site::ticketAssetsFullPath($site->slug_name);
|
||||
if (File::exists($path)) {
|
||||
File::deleteDirectory($path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* HasMany Message relation
|
||||
* The users of type client that would have access to this site tickets
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* HasMany Ticket relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function tickets()
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'site_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo Brand relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function brand()
|
||||
{
|
||||
return $this->belongsTo(Brand::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsToMany Project relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function projects()
|
||||
{
|
||||
return $this->belongsToMany(Project::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* MorphMany Log relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
|
||||
*/
|
||||
public function logs()
|
||||
{
|
||||
return $this->morphMany(Log::class, 'parent');
|
||||
}
|
||||
|
||||
/**
|
||||
* MorphMany File relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
return $this->morphMany(File::class, 'parent');
|
||||
}
|
||||
|
||||
public function getSlugNameAttribute()
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Getter slugName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSlugNameAttribute(): string
|
||||
{
|
||||
return Str::slug($this->name);
|
||||
}
|
||||
|
||||
public static function ticketAssetsFullPath(string $str)
|
||||
{
|
||||
return public_path(self::ticketAssetsPath($str));
|
||||
}
|
||||
|
||||
public static function ticketAssetsPath(string $str)
|
||||
{
|
||||
return sprintf('fichiers/%s', $str);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ use App\Events\NotifySubscriptions;
|
|||
use App\Events\TicketStatusChanged;
|
||||
use App\Jobs\SendTicketMentionMail;
|
||||
use App\Mail\TicketMention;
|
||||
use App\Models\Credit\Account;
|
||||
use App\Models\Credit\Estimation;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
use Kwaadpepper\Enum\Traits\CastsEnums;
|
||||
|
||||
|
|
@ -22,11 +23,30 @@ class Ticket extends Model
|
|||
use HasFactory;
|
||||
use CastsEnums;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to Enums.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enumCasts = [
|
||||
'type' => TicketType::class,
|
||||
'status' => TicketStatus::class
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'is_internal' => 'bool'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'site_id',
|
||||
'type',
|
||||
|
|
@ -35,10 +55,15 @@ class Ticket extends Model
|
|||
'is_internal'
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function (Ticket $ticket) {
|
||||
// If evolution force the ticket status on estimateTodo
|
||||
// If evolution force the ticket status on estimateTodo.
|
||||
if ($ticket->type->equals(TicketType::evolution())) {
|
||||
$ticket->status = TicketStatus::estimateTodo();
|
||||
}
|
||||
|
|
@ -64,42 +89,83 @@ class Ticket extends Model
|
|||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* HasOne Credit\Estimation relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function estimation()
|
||||
{
|
||||
return $this->hasOne(Estimation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo Site relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function site()
|
||||
{
|
||||
return $this->belongsTo(Site::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo User relation
|
||||
* The user that created the ticket
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsTo User relation
|
||||
* The user that is in charge of the ticket
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function assignedUser()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'assigned_to');
|
||||
}
|
||||
|
||||
/**
|
||||
* HasMany Message relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function messages()
|
||||
{
|
||||
return $this->hasMany(Message::class, 'ticket_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsToMany User relation
|
||||
* The Users that are following this ticket
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function subscriptions()
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'subscriptions');
|
||||
}
|
||||
|
||||
public static function saveFile(Site $site, Ticket $ticket, TicketType $type, UploadedFile $file)
|
||||
/**
|
||||
* BelongsToMany Label relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function labels()
|
||||
{
|
||||
$siteAssetFullPath = Site::ticketAssetsFullPath($site->slug_name);
|
||||
$fileAssetPath = Site::ticketAssetsPath($site->slug_name);
|
||||
$fileName = sprintf(
|
||||
'%s-%s-%d-%d.%s',
|
||||
$site->slug_name,
|
||||
$type->getDefinition(),
|
||||
$ticket->id,
|
||||
rand(),
|
||||
$file->extension()
|
||||
);
|
||||
$file->move(sprintf('%s/%d', $siteAssetFullPath, $ticket->id), $fileName);
|
||||
return sprintf('%s/%d/%s', $fileAssetPath, $ticket->id, $fileName);
|
||||
return $this->belongsToMany(Label::class, 'label_ticket');
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Parse message @user tags and send emails to concerned users
|
||||
* returns and array with to users notified
|
||||
|
|
@ -110,7 +176,7 @@ class Ticket extends Model
|
|||
public function sendBeenMentionnedToUser(Message $message): Collection
|
||||
{
|
||||
$ticket = $this;
|
||||
// find @user + send mail si un seul user est trouvé
|
||||
// Find @user + send mail si un seul user est trouvé.
|
||||
$usersNotified = collect([]);
|
||||
$matches = [];
|
||||
$ticket->load('site');
|
||||
|
|
@ -128,7 +194,7 @@ class Ticket extends Model
|
|||
->orWhere('role', Role::developper()->value);
|
||||
})->firstOrFail();
|
||||
|
||||
if ($ticket->is_internal === 1) {
|
||||
if ($ticket->is_internal) {
|
||||
if ($user->isAdmin()) {
|
||||
$usersNotified->add($user);
|
||||
SendTicketMentionMail::dispatchAfterResponse(
|
||||
|
|
@ -149,4 +215,37 @@ class Ticket extends Model
|
|||
}//end foreach
|
||||
return $usersNotified;
|
||||
}
|
||||
|
||||
/**
|
||||
* full_name getter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFullNameAttribute(): string
|
||||
{
|
||||
return trans('Ticket n° :ticketId : :ticketSiteName - :ticketTypeLabel', [
|
||||
'ticketId' => $this->id,
|
||||
'ticketSiteName' => $this->site->name,
|
||||
'ticketTypeLabel' => $this->type->label
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an account has the same brand as this ticket.
|
||||
*
|
||||
* @param Account $account
|
||||
* @return boolean
|
||||
*/
|
||||
public function accountIsValidForThisTicket(Account $account): bool
|
||||
{
|
||||
/** @var \App\Models\Brand|null */
|
||||
if (!($siteBrand = optional($this->site)->brand)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var \App\Models\Brand */
|
||||
$accountBrand = $account->brand;
|
||||
|
||||
return !\is_null($accountBrand->id) and $accountBrand->id === $siteBrand->id;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ namespace App\Models;
|
|||
use App\Enums\Role;
|
||||
use App\Jobs\SendUserCreatedEmail;
|
||||
use App\Notifications\ResetPassword;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
|
||||
|
|
@ -24,17 +26,29 @@ class User extends Authenticatable
|
|||
use Notifiable;
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to Enums.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enumCasts = [
|
||||
'role' => Role::class
|
||||
];
|
||||
|
||||
/**
|
||||
* Crud Label
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudLabelColumn = 'name';
|
||||
|
||||
/**
|
||||
* Crud Value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $crudValueColumn = 'id';
|
||||
|
||||
// stores unhashed pwd
|
||||
public $tmpPass = null;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
|
|
@ -67,7 +81,12 @@ class User extends Authenticatable
|
|||
'email_verified_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function __construct($attributes = [])
|
||||
/**
|
||||
* User
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
self::$editableProperties = [
|
||||
'name' => [
|
||||
|
|
@ -148,6 +167,11 @@ class User extends Authenticatable
|
|||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function (User $user) {
|
||||
|
|
@ -158,46 +182,89 @@ class User extends Authenticatable
|
|||
if (Hash::needsRehash($user->password)) {
|
||||
$user->password = Hash::make($user->password);
|
||||
}
|
||||
Cache::flush('AdminUsers');
|
||||
});
|
||||
static::updating(function (User $user) {
|
||||
if (Hash::needsRehash($user->password)) {
|
||||
$user->password = Hash::make($user->password);
|
||||
}
|
||||
Cache::flush('AdminUsers');
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* HasMany Ticket relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function tickets()
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* HasMany Message relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function messages()
|
||||
{
|
||||
return $this->hasMany(Messages::class, 'user_id');
|
||||
return $this->hasMany(Message::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsToMany Ticket relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function subscriptions()
|
||||
{
|
||||
return $this->belongsToMany(Ticket::class, 'subscriptions');
|
||||
}
|
||||
|
||||
/**
|
||||
* BelongsToMany Site relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function sites()
|
||||
{
|
||||
return $this->belongsToMany(Site::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* HasMany Schedule relation
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function schedules()
|
||||
{
|
||||
return $this->hasMany(Schedule::class);
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Send User creation confirm mail
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendMailConfirm()
|
||||
{
|
||||
dispatch(new SendUserCreatedEmail($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send User reset password mail
|
||||
*
|
||||
* @param mixed $token
|
||||
* @return void
|
||||
*/
|
||||
public function sendPasswordResetNotification($token)
|
||||
{
|
||||
/** @var string $token */
|
||||
$this->notify(new ResetPassword($token));
|
||||
}
|
||||
|
||||
|
|
@ -211,4 +278,16 @@ class User extends Authenticatable
|
|||
return ($this->role->equals(Role::developper()) or
|
||||
$this->role->equals(Role::admin()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get All Admin users
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function adminUsers(): Collection
|
||||
{
|
||||
return Cache::rememberForever('AdminUsers', function () {
|
||||
return self::whereIn('role', [Role::admin(), Role::developper()])->get();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class ResetPassword extends ResetPasswordNotification
|
|||
/**
|
||||
* Get the reset password notification mail message for the given URL.
|
||||
*
|
||||
* @param string $url
|
||||
* @param mixed $url
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
protected function buildMailMessage($url)
|
||||
|
|
@ -22,6 +22,7 @@ class ResetPassword extends ResetPasswordNotification
|
|||
->subject(trans('Reinitialisation de mot de passe'))
|
||||
//phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
->line(trans('Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte.'))
|
||||
/** @var string $url */
|
||||
->action(trans('Réinitialiser le mot de passe'), $url)
|
||||
//phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
->line(trans('Ce lien de réinitialisation de mot de passe expirera dans :count minutes. ', ['count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]))
|
||||
|
|
|
|||
39
app/Policies/AccountPolicy.php
Normal file
39
app/Policies/AccountPolicy.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserRoleRules as URR;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
|
||||
|
||||
class AccountPolicy extends RootPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Init policies on call
|
||||
*
|
||||
* @param User $user
|
||||
* @param Model $model
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
*/
|
||||
public function setPolicies(User $user, Model $model = null)
|
||||
{
|
||||
/**
|
||||
* This first rule to return true in a policy action authorises the action
|
||||
*/
|
||||
$this->policies = [
|
||||
'account' => [
|
||||
'viewAny' => URR::make('allowEveryOne'),
|
||||
'view' => URR::make('denyEveryOne'),
|
||||
'update' => URR::make('denyEveryOne'),
|
||||
'create' => URR::make('userIsAdmin', $user),
|
||||
'delete' => URR::make('userIsAdmin', $user),
|
||||
'credit' => URR::make('userIsAdmin', $user)
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
38
app/Policies/BrandPolicy.php
Normal file
38
app/Policies/BrandPolicy.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserRoleRules as URR;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
|
||||
|
||||
class BrandPolicy extends RootPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Init policies on call
|
||||
*
|
||||
* @param User $user
|
||||
* @param Model $model
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
*/
|
||||
public function setPolicies(User $user, Model $model = null)
|
||||
{
|
||||
/**
|
||||
* This first rule to return true in a policy action authorises the action
|
||||
*/
|
||||
$this->policies = [
|
||||
'brand' => [
|
||||
'viewAny' => URR::make('userIsAdmin', $user),
|
||||
'view' => URR::make('userIsAdmin', $user),
|
||||
'update' => URR::make('userIsAdmin', $user),
|
||||
'create' => URR::make('userIsAdmin', $user),
|
||||
'delete' => URR::make('userIsAdmin', $user)
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
34
app/Policies/CreditLogPolicy.php
Normal file
34
app/Policies/CreditLogPolicy.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserRoleRules as URR;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
|
||||
|
||||
class CreditLogPolicy extends RootPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Init policies on call
|
||||
*
|
||||
* @param User $user
|
||||
* @param Model $model
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
*/
|
||||
public function setPolicies(User $user, Model $model = null)
|
||||
{
|
||||
/**
|
||||
* This first rule to return true in a policy action authorises the action
|
||||
*/
|
||||
$this->policies = [
|
||||
'creditlog' => [
|
||||
'viewAny' => URR::make('allowEveryOne')
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
34
app/Policies/EstimationPolicy.php
Normal file
34
app/Policies/EstimationPolicy.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\EstimationStaticRules;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
|
||||
|
||||
class EstimationPolicy extends RootPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Init policies on call
|
||||
*
|
||||
* @param User $user
|
||||
* @param Model $model
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
*/
|
||||
public function setPolicies(User $user, Model $model = null)
|
||||
{
|
||||
/**
|
||||
* This first rule to return true in a policy action authorises the action
|
||||
*/
|
||||
$this->policies = [
|
||||
'estimation' => [
|
||||
'answer' => EstimationStaticRules::make('estimateIsPending', $model)
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
37
app/Policies/LabelPolicy.php
Normal file
37
app/Policies/LabelPolicy.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserRoleRules as URR;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
|
||||
|
||||
class LabelPolicy extends RootPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Init policies on call
|
||||
*
|
||||
* @param User $user
|
||||
* @param Model $model
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
*/
|
||||
public function setPolicies(User $user, Model $model = null)
|
||||
{
|
||||
/**
|
||||
* This first rule to return true in a policy action authorises the action
|
||||
*/
|
||||
$this->policies = [
|
||||
'label' => [
|
||||
'viewAny' => URR::make('userIsAdmin', $user),
|
||||
'create' => URR::make('userIsAdmin', $user),
|
||||
'update' => URR::make('userIsAdmin', $user),
|
||||
'delete' => URR::make('userIsAdmin', $user)
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -17,11 +17,11 @@ class LogPolicy extends RootPolicy
|
|||
/**
|
||||
* Init policies on call
|
||||
*
|
||||
* @param User $user
|
||||
* @param User $user
|
||||
* @param Model $model
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
*/
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
public function setPolicies(User $user, Model $model = null)
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
25
app/Policies/Rules/EstimationStaticRules.php
Normal file
25
app/Policies/Rules/EstimationStaticRules.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies\Rules;
|
||||
|
||||
use App\Enums\Credit\EstimationStatus;
|
||||
use App\Models\Credit\Estimation;
|
||||
use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
|
||||
|
||||
/**
|
||||
* Estimation Policiy Static Rules
|
||||
* ! Rules has to return boolean value
|
||||
*/
|
||||
abstract class EstimationStaticRules extends StaticRules
|
||||
{
|
||||
/**
|
||||
* Estimation is pending
|
||||
*
|
||||
* @param Estimation $model
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function estimateIsPending(Estimation $model): bool
|
||||
{
|
||||
return $model->status->equals(EstimationStatus::pending());
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,19 @@ use App\Models\User;
|
|||
use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
|
||||
|
||||
/**
|
||||
* Userlevel Policiy Static Rules
|
||||
* Log Policiy Static Rules
|
||||
* ! Rules has to return boolean value
|
||||
*/
|
||||
abstract class LogStaticRules extends StaticRules
|
||||
{
|
||||
protected static function logIsOwnedByUser(User $user, Log $log)
|
||||
/**
|
||||
* Log Is owned by user.
|
||||
*
|
||||
* @param User $user
|
||||
* @param Log $log
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function logIsOwnedByUser(User $user, Log $log): bool
|
||||
{
|
||||
return $log->user->id === $user->id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,78 @@ use App\Models\Ticket;
|
|||
use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
|
||||
|
||||
/**
|
||||
* Userlevel Policiy Static Rules
|
||||
* Ticket Policiy Static Rules
|
||||
* ! Rules has to return boolean value
|
||||
*/
|
||||
abstract class TicketStaticRules extends StaticRules
|
||||
{
|
||||
protected static function ticketIsNotArchived(Ticket $ticket)
|
||||
/**
|
||||
* Ticket is not in production
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function ticketIsNotInProduction(Ticket $ticket): bool
|
||||
{
|
||||
return !$ticket->status->equals(TicketStatus::production());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticket is not archived
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function ticketIsNotArchived(Ticket $ticket): bool
|
||||
{
|
||||
return !$ticket->status->equals(TicketStatus::archived());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticket has brand which has at least one account
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function ticketsBrandHasAnAccount(Ticket $ticket): bool
|
||||
{
|
||||
/** @var \App\Models\Brand|null */
|
||||
if (!($brand = $ticket->site->brand)) {
|
||||
return false;
|
||||
}
|
||||
return (bool)$brand->accounts()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticket does already have an estimation
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function ticketDoesHaveEstimation(Ticket $ticket): bool
|
||||
{
|
||||
return $ticket->estimation()->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticket does not already have an estimation
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function ticketDoesntHaveEstimation(Ticket $ticket): bool
|
||||
{
|
||||
return !$ticket->estimation()->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticket is not internal
|
||||
*
|
||||
* @param Ticket $ticket
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function ticketIsNotInternal(Ticket $ticket): bool
|
||||
{
|
||||
return !(bool)$ticket->is_internal;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,32 +7,60 @@ use App\Models\User;
|
|||
use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
|
||||
|
||||
/**
|
||||
* Userlevel Policiy Static Rules
|
||||
* User Policiy Static Rules
|
||||
* ! Rules has to return boolean value
|
||||
*/
|
||||
abstract class UserRoleRules extends StaticRules
|
||||
{
|
||||
protected static function userIsAdmin(User $user)
|
||||
/**
|
||||
* User is admin
|
||||
*
|
||||
* @param User $user
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function userIsAdmin(User $user): bool
|
||||
{
|
||||
return $user->isAdmin();
|
||||
}
|
||||
|
||||
protected static function userIsDev(User $user)
|
||||
/**
|
||||
* User Is Dev
|
||||
*
|
||||
* @param User $user
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function userIsDev(User $user): bool
|
||||
{
|
||||
return $user->role->equals(Role::developper());
|
||||
}
|
||||
|
||||
protected static function userIsClient(User $user)
|
||||
/**
|
||||
* User Is Client
|
||||
*
|
||||
* @param User $user
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function userIsClient(User $user): bool
|
||||
{
|
||||
return $user->role->equals(Role::client());
|
||||
}
|
||||
|
||||
protected static function allowEveryOne()
|
||||
/**
|
||||
* Allow every one
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function allowEveryOne(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static function denyEveryOne()
|
||||
/**
|
||||
* Deny everyone
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function denyEveryOne(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ class SitePolicy extends RootPolicy
|
|||
/**
|
||||
* Init policies on call
|
||||
*
|
||||
* @param User $user
|
||||
* @param User $user
|
||||
* @param Model $model
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
*/
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
public function setPolicies(User $user, Model $model = null)
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -18,11 +18,10 @@ class TicketPolicy extends RootPolicy
|
|||
/**
|
||||
* Init policies on call
|
||||
*
|
||||
* @param User $user
|
||||
* @param User $user
|
||||
* @param Model $model
|
||||
* @return void
|
||||
*/
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
|
||||
public function setPolicies(User $user, Model $model = null)
|
||||
{
|
||||
/**
|
||||
|
|
@ -51,7 +50,28 @@ class TicketPolicy extends RootPolicy
|
|||
URR::make('userIsAdmin', $user),
|
||||
TicketStaticRules::make('ticketIsNotArchived', $model),
|
||||
]),
|
||||
'reopen' => URR::make('userIsClient', $user)
|
||||
'reopen' => URR::make('userIsClient', $user),
|
||||
'assignTo' => StaticRules::and([
|
||||
URR::make('userIsAdmin', $user),
|
||||
TicketStaticRules::make('ticketIsNotInProduction', $model),
|
||||
TicketStaticRules::make('ticketIsNotArchived', $model)
|
||||
]),
|
||||
'assignTickets' => URR::make('userIsAdmin', $user),
|
||||
'assignLabels' => URR::make('userIsAdmin', $user),
|
||||
'estimate' => StaticRules::and([
|
||||
URR::make('userIsAdmin', $user),
|
||||
TicketStaticRules::make('ticketsBrandHasAnAccount', $model),
|
||||
TicketStaticRules::make('ticketDoesntHaveEstimation', $model),
|
||||
TicketStaticRules::make('ticketIsNotInternal', $model),
|
||||
TicketStaticRules::make('ticketIsNotArchived', $model)
|
||||
]),
|
||||
'validateEstimate' => StaticRules::and([
|
||||
URR::make('userIsClient', $user),
|
||||
TicketStaticRules::make('ticketsBrandHasAnAccount', $model),
|
||||
TicketStaticRules::make('ticketDoesHaveEstimation', $model),
|
||||
TicketStaticRules::make('ticketIsNotInternal', $model),
|
||||
TicketStaticRules::make('ticketIsNotArchived', $model)
|
||||
])
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Brand;
|
||||
use App\Models\Credit\Account;
|
||||
use App\Models\File;
|
||||
use App\Models\Label;
|
||||
use App\Models\Log;
|
||||
use App\Models\MacroSchedule\Marker;
|
||||
use App\Models\Project;
|
||||
|
|
@ -35,16 +38,16 @@ class RouteServiceProvider extends ServiceProvider
|
|||
{
|
||||
$this->configureRateLimiting();
|
||||
|
||||
$this->routes(function () {
|
||||
Route::prefix('api')
|
||||
->middleware('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/api.php'));
|
||||
$this->cspReportRoute();
|
||||
|
||||
$this->routes(function () {
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web.php'));
|
||||
});
|
||||
Route::model('account', Account::class);
|
||||
Route::model('brand', Brand::class);
|
||||
Route::model('label', Label::class);
|
||||
Route::model('marker', Marker::class);
|
||||
Route::model('file', File::class);
|
||||
Route::model('log', Log::class);
|
||||
|
|
@ -70,4 +73,42 @@ class RouteServiceProvider extends ServiceProvider
|
|||
return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* * Register CSP report URI
|
||||
*
|
||||
* Put this function inside RouteServiceProvider,
|
||||
* and call it in RouteServiceProvider::boot
|
||||
*
|
||||
* ! Use this in conjonction with kwaadpepper/exception-handler
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cspReportRoute()
|
||||
{
|
||||
// * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware
|
||||
\Illuminate\Support\Facades\Route::post('/csp-report', function () {
|
||||
$isCspRequest = request()->header('Content-Type') === 'application/csp-report';
|
||||
if (!$isCspRequest or !request()->json('csp-report') or !is_array(request()->json('csp-report'))) {
|
||||
abort(401);
|
||||
}
|
||||
// * Flatten if needed
|
||||
$cspReport = collect(request()->json('csp-report'))
|
||||
->flatMap(function ($v, $k) {
|
||||
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
|
||||
})->all();
|
||||
// * Filter array keys
|
||||
$cspReport = collect(array_intersect_key($cspReport, [
|
||||
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '',
|
||||
'referrer' => '', 'violated-directive' => ''
|
||||
]));
|
||||
report(new \Illuminate\Http\Client\HttpClientException(sprintf(
|
||||
"CSP REPORT for %s : \n %s",
|
||||
$cspReport->get('document-uri'),
|
||||
$cspReport->implode("\n")
|
||||
)));
|
||||
})->middleware('throttle:5,2')
|
||||
->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class)
|
||||
->name('cspReportUri');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ldcweb/supportweb",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.0",
|
||||
"type": "project",
|
||||
"description": "The Laravel Framework.",
|
||||
"keywords": [
|
||||
|
|
@ -15,10 +15,11 @@
|
|||
"fideloper/proxy": "^4.4",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"kwaadpepper/crud-policies": "^0.3.6",
|
||||
"kwaadpepper/crud-policies": "^0.4.0",
|
||||
"kwaadpepper/enum": "^2.1",
|
||||
"kwaadpepper/exception-handler": "^0.0.5",
|
||||
"laravel-lang/lang": "~8.0",
|
||||
"laravel-validation-rules/colour": "^1.6",
|
||||
"laravel/framework": "^8.12",
|
||||
"laravel/tinker": "^2.5",
|
||||
"laravel/ui": "^3.3",
|
||||
|
|
|
|||
67
composer.lock
generated
67
composer.lock
generated
|
|
@ -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": "2a618153177d1ef96c1f71551d669f8c",
|
||||
"content-hash": "22dfaf5696ddcb98ef8764d0f03a6ba0",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
|
|
@ -1505,16 +1505,16 @@
|
|||
},
|
||||
{
|
||||
"name": "kwaadpepper/crud-policies",
|
||||
"version": "0.3.6",
|
||||
"version": "0.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Kwaadpepper/crud-policies.git",
|
||||
"reference": "205b8a16c99c362fc78422eeb31c0ba618a3d351"
|
||||
"reference": "91616b1788fb581a15473f435fad2c0dbdd07f4c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Kwaadpepper/crud-policies/zipball/205b8a16c99c362fc78422eeb31c0ba618a3d351",
|
||||
"reference": "205b8a16c99c362fc78422eeb31c0ba618a3d351",
|
||||
"url": "https://api.github.com/repos/Kwaadpepper/crud-policies/zipball/91616b1788fb581a15473f435fad2c0dbdd07f4c",
|
||||
"reference": "91616b1788fb581a15473f435fad2c0dbdd07f4c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1565,9 +1565,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Kwaadpepper/crud-policies/issues",
|
||||
"source": "https://github.com/Kwaadpepper/crud-policies/tree/0.3.6"
|
||||
"source": "https://github.com/Kwaadpepper/crud-policies/tree/0.4.0"
|
||||
},
|
||||
"time": "2022-05-04T13:19:25+00:00"
|
||||
"time": "2022-05-31T15:35:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "kwaadpepper/enum",
|
||||
|
|
@ -1743,6 +1743,59 @@
|
|||
},
|
||||
"time": "2021-04-14T17:41:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel-validation-rules/colour",
|
||||
"version": "1.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel-validation-rules/colour.git",
|
||||
"reference": "1e7ab2420879738f45adcf0320f8c571789e9fb4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel-validation-rules/colour/zipball/1e7ab2420879738f45adcf0320f8c571789e9fb4",
|
||||
"reference": "1e7ab2420879738f45adcf0320f8c571789e9fb4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/contracts": "^5.5|^6.0|^7.0|^8.0|^9.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^5.0|^7.0",
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"LVR\\Colour\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ashley Clarke",
|
||||
"email": "me@ashleyclarke.me"
|
||||
},
|
||||
{
|
||||
"name": "Scott Robinson",
|
||||
"email": "scott@dor.ky"
|
||||
}
|
||||
],
|
||||
"description": "Validate colours",
|
||||
"keywords": [
|
||||
"colour",
|
||||
"laravel",
|
||||
"validation"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel-validation-rules/colour/issues",
|
||||
"source": "https://github.com/laravel-validation-rules/colour/tree/1.6.0"
|
||||
},
|
||||
"time": "2022-02-08T08:13:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v8.83.13",
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ return [
|
|||
// * Application custom
|
||||
'TicketType' => App\Enums\TicketType::class,
|
||||
'TicketStatus' => App\Enums\TicketStatus::class,
|
||||
'Utils' => App\Lib\Utils::class,
|
||||
'ToolboxHelper' => App\Lib\ToolboxHelper::class,
|
||||
'CrudAction' => App\Enums\CrudAction::class,
|
||||
'CrudType' => App\Enums\CrudType::class,
|
||||
'Role' => App\Enums\Role::class,
|
||||
|
|
|
|||
|
|
@ -39,13 +39,7 @@ return [
|
|||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'driver' => 'token',
|
||||
'provider' => 'users',
|
||||
'hash' => false,
|
||||
],
|
||||
]
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
50
config/crud-policies.php
Normal file
50
config/crud-policies.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
* The url prefix of Omen file manager
|
||||
** default is 'crud-policies'
|
||||
*! The url prefix must not match any file in public folder
|
||||
*/
|
||||
'urlPrefix' => 'crud-policies',
|
||||
|
||||
/**
|
||||
* You shall protect routes
|
||||
* These are applied to crud routes directly as middlewares
|
||||
*/
|
||||
'middlewares' => [
|
||||
'web',
|
||||
'auth'
|
||||
],
|
||||
|
||||
/**
|
||||
* The public path where are stored
|
||||
* crud-policies asset files
|
||||
*
|
||||
* Change this if you want to modify
|
||||
* published assets folder under /public
|
||||
** default is 'vendor/crud-policies'
|
||||
*/
|
||||
'assetPath' => 'vendor/crud-policies',
|
||||
|
||||
/**
|
||||
* This is the package layout that will be used
|
||||
* to display crud elements
|
||||
*
|
||||
* You can publish views to know what sections it
|
||||
* has to display
|
||||
*/
|
||||
'viewLayout' => 'crud-policies::crud.layout',
|
||||
|
||||
/**
|
||||
* Controls whether to display flash messages
|
||||
* on actions such as create, update or delete.
|
||||
*/
|
||||
'displayAlerts' => true,
|
||||
|
||||
/**
|
||||
* Controls whether to show delete button on index routes
|
||||
*/
|
||||
'deleteOnIndex' => true
|
||||
];
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
* if ( ! $this->config->get('purifier.finalize')) {
|
||||
* $config->autoFinalize = false;
|
||||
* }
|
||||
* $config->loadArray($this->getConfig());
|
||||
* $config->load [$this->getConfig());
|
||||
*
|
||||
* You must NOT delete the default settings
|
||||
* anything in settings should be compacted with params that needed to instance HTMLPurifier_Config.
|
||||
|
|
@ -25,26 +25,26 @@ return [
|
|||
'cacheFileMode' => 0755,
|
||||
'settings' => [
|
||||
'default' => [
|
||||
'HTML.SafeIframe' => true,
|
||||
// * For iframes
|
||||
'URI.SafeIframeRegexp' => "%^(http://|https://|//)(vimeo.com)%",
|
||||
'HTML.Doctype' => 'HTML 4.01 Transitional',
|
||||
'HTML.Allowed' => 'h1,h2,h3,h4,h5,h6,div,b,strong,i,em,u,a[href|title],ul,ol,li,p[style],br,span[style],img[width|height|alt|src]',
|
||||
'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align',
|
||||
'HTML.Allowed' => 'h1[style],h2[style],h3[style],h4[style],h5[style],h6[style],figure[class|style],oembed[url],div[style],b,strong,i,em,u,a[href|title|target|rel],ul[style],ol[style],dl[style],li,p[style],br,span[style|class],img[width|height|alt|src|style|class|title],iframe[src|width|height|class|frameborder|style],table[style|align|border|cellspacing|cellpadding|width],thead,tbody,th[colspan|align|valign|style],tr[align|valign|style],td[style|colspan|rowspan|scope|align|valign],hr,dl,dt',
|
||||
'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align,list-style-type,float,width,height,vertical-align,border-color',
|
||||
'AutoFormat.AutoParagraph' => false,
|
||||
'AutoFormat.RemoveEmpty' => false,
|
||||
'AutoFormat.RemoveEmpty.RemoveNbsp' => false,
|
||||
],
|
||||
'test' => [
|
||||
'Attr.EnableID' => 'true',
|
||||
],
|
||||
"youtube" => [
|
||||
"HTML.SafeIframe" => 'true',
|
||||
"URI.SafeIframeRegexp" => "%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/)%",
|
||||
"URI.SafeIframeRegexp" => "%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/|vimeo.com/)%",
|
||||
],
|
||||
'custom_definition' => [
|
||||
'id' => 'html5-definitions',
|
||||
'rev' => 1,
|
||||
'debug' => false,
|
||||
'debug' => true,
|
||||
'elements' => [
|
||||
// http://developers.whatwg.org/sections.html
|
||||
// * http://developers.whatwg.org/sections.html
|
||||
['section', 'Block', 'Flow', 'Common'],
|
||||
['nav', 'Block', 'Flow', 'Common'],
|
||||
['article', 'Block', 'Flow', 'Common'],
|
||||
|
|
@ -52,15 +52,16 @@ return [
|
|||
['header', 'Block', 'Flow', 'Common'],
|
||||
['footer', 'Block', 'Flow', 'Common'],
|
||||
|
||||
// Content model actually excludes several tags, not modelled here
|
||||
// * Content model actually excludes several tags, not modelled here
|
||||
['address', 'Block', 'Flow', 'Common'],
|
||||
['hgroup', 'Block', 'Required: h1 | h2 | h3 | h4 | h5 | h6', 'Common'],
|
||||
|
||||
// http://developers.whatwg.org/grouping-content.html
|
||||
// * http://developers.whatwg.org/grouping-content.html
|
||||
['figure', 'Block', 'Optional: (figcaption, Flow) | (Flow, figcaption) | Flow', 'Common'],
|
||||
['figcaption', 'Inline', 'Flow', 'Common'],
|
||||
['oembed', 'Inline', 'Flow', 'Common'],
|
||||
|
||||
// http://developers.whatwg.org/the-video-element.html#the-video-element
|
||||
// * http://developers.whatwg.org/the-video-element.html#the-video-element
|
||||
['video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [
|
||||
'src' => 'URI',
|
||||
'type' => 'Text',
|
||||
|
|
@ -75,7 +76,7 @@ return [
|
|||
'type' => 'Text',
|
||||
]],
|
||||
|
||||
// http://developers.whatwg.org/text-level-semantics.html
|
||||
// * http://developers.whatwg.org/text-level-semantics.html
|
||||
['s', 'Inline', 'Inline', 'Common'],
|
||||
['var', 'Inline', 'Inline', 'Common'],
|
||||
['sub', 'Inline', 'Inline', 'Common'],
|
||||
|
|
@ -83,7 +84,7 @@ return [
|
|||
['mark', 'Inline', 'Inline', 'Common'],
|
||||
['wbr', 'Inline', 'Empty', 'Core'],
|
||||
|
||||
// http://developers.whatwg.org/edits.html
|
||||
// * http://developers.whatwg.org/edits.html
|
||||
['ins', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']],
|
||||
['del', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']],
|
||||
],
|
||||
|
|
@ -95,14 +96,24 @@ return [
|
|||
['tr', 'width', 'Text'],
|
||||
['tr', 'height', 'Text'],
|
||||
['tr', 'border', 'Text'],
|
||||
['figure', 'class', 'Text'],
|
||||
['figure', 'style', 'Text'],
|
||||
['oembed', 'url', 'Text'],
|
||||
['span', 'style', 'Text'],
|
||||
['img', 'style', 'Text'],
|
||||
],
|
||||
],
|
||||
'custom_attributes' => [
|
||||
['a', 'target', 'Enum#_blank,_self,_target,_top'],
|
||||
['ol', 'style', 'Text'],
|
||||
['figure', 'style', 'Text'],
|
||||
['img', 'style', 'Text'],
|
||||
['span', 'style', 'Text'],
|
||||
['oembed', 'url', 'Text'],
|
||||
],
|
||||
'custom_elements' => [
|
||||
['u', 'Inline', 'Inline', 'Common'],
|
||||
['oembed', 'Inline', 'Inline', 'Common'],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUsersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('password');
|
||||
$table->integer('role')->default(2);
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateTickets extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tickets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('created_by')->constrained('users');
|
||||
$table->foreignId('project_id')->constrained('projects');
|
||||
$table->integer('type');
|
||||
$table->text('text');
|
||||
$table->string('file')->nullable();
|
||||
$table->integer('status')->default(1);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tickets');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateUsers extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->foreignId('project_id')->nullable()->after('role')->constrained('projects');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropConstrainedForeignId('project_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateMessages extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('messages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('ticket_id')->constrained('tickets');
|
||||
$table->foreignId('user_id')->nullable()->constrained('users');
|
||||
$table->text('message');
|
||||
$table->string('file')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('messages');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AlterTickets extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tickets', function(Blueprint $table) {
|
||||
$table->string('file2')->nullable()->after('file');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('tickets', function(Blueprint $table) {
|
||||
$table->dropColumn('file2');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateSubscriptions extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('subscriptions', function (Blueprint $table) {
|
||||
$table->foreignId('ticket_id')->constrained('tickets');
|
||||
$table->foreignId('user_id')->constrained('users');
|
||||
$table->primary(['ticket_id', 'user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('subscriptions');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\TicketStatus;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateTicketsStatusWithEnumStatus extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
foreach (Ticket::where('status', 4)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::archived();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\TicketStatus;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateTicketsStatusWithEnumStatus2 extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
foreach (Ticket::where('status', 5)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::production();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
foreach (Ticket::where('status', 4)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::recette();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
foreach (Ticket::where('status', 3)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::processed();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\TicketStatus;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateTicketsStatusWithEnumStatus3 extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
//save saveQuietly (without events)
|
||||
foreach (Ticket::where('status', 9)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::archived();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
foreach (Ticket::where('status', 6)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::production();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
foreach (Ticket::where('status', 5)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::recette();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
foreach (Ticket::where('status', 4)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::processed();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
foreach (Ticket::where('status', 3)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::waiting();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
foreach (Ticket::where('status', 2)->get() as $ticket) {
|
||||
$ticket->status = TicketStatus::inprogress();
|
||||
$ticket->saveQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\SiteUser;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProjectUserTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('project_user', function (Blueprint $table) {
|
||||
$table->foreignId('project_id')->constrained('projects');
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->unique(['project_id', 'user_id']);
|
||||
});
|
||||
/** @var \App\Models\User */
|
||||
foreach (User::all() as $user) {
|
||||
if ($user->project_id) {
|
||||
$uP = new SiteUser();
|
||||
$uP->project_id = $user->project_id;
|
||||
$uP->user_id = $user->id;
|
||||
$uP->saveOrFail();
|
||||
}
|
||||
}
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropForeign(['project_id']);
|
||||
$table->dropColumn('project_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->foreignId('project_id')->nullable()
|
||||
->after('role')->constrained('projects');
|
||||
});
|
||||
/** @var \App\Models\SiteUser */
|
||||
foreach (SiteUser::all() as $uP) {
|
||||
try {
|
||||
/** @var \App\Models\User */
|
||||
$user = User::whereId($uP->user_id)->firstOrFail();
|
||||
$user->project_id = $uP->project_id;
|
||||
$user->saveOrFail();
|
||||
//phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
||||
} catch (ModelNotFoundException $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
Schema::dropIfExists('project_user');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddSoftDeleteOnUsers extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddSoftDeleteOnProjects extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RenameProjectsTableIntoSitesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::rename('projects', 'sites');
|
||||
Schema::table('project_user', function (Blueprint $table) {
|
||||
$table->dropForeign('project_user_project_id_foreign');
|
||||
$table->dropUnique(['project_id', 'user_id']);
|
||||
});
|
||||
Schema::rename('project_user', 'site_user');
|
||||
Schema::table('site_user', function (Blueprint $table) {
|
||||
$table->renameColumn('project_id', 'site_id');
|
||||
$table->foreign('site_id')->references('id')->on('sites');
|
||||
$table->unique(['site_id', 'user_id']);
|
||||
});
|
||||
Schema::table('tickets', function (Blueprint $table) {
|
||||
$table->dropForeign('tickets_project_id_foreign');
|
||||
$table->renameColumn('project_id', 'site_id');
|
||||
$table->foreign('site_id')->references('id')->on('sites');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::rename('sites', 'projects');
|
||||
Schema::table('site_user', function (Blueprint $table) {
|
||||
$table->dropForeign('site_user_site_id_foreign');
|
||||
$table->dropUnique(['site_id', 'user_id']);
|
||||
});
|
||||
Schema::rename('site_user', 'project_user');
|
||||
Schema::table('project_user', function (Blueprint $table) {
|
||||
$table->renameColumn('site_id', 'project_id');
|
||||
$table->foreign('project_id')->references('id')->on('projects');
|
||||
$table->unique(['project_id', 'user_id']);
|
||||
});
|
||||
Schema::table('tickets', function (Blueprint $table) {
|
||||
$table->dropForeign('tickets_site_id_foreign');
|
||||
$table->renameColumn('site_id', 'project_id');
|
||||
$table->foreign('project_id')->references('id')->on('projects');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProjectsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('projects', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('status');
|
||||
$table->text('description');
|
||||
$table->text('todo')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('projects');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProjectSiteTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('project_site', function (Blueprint $table) {
|
||||
$table->foreignId('project_id')->constrained('projects')->cascadeOnDelete();
|
||||
$table->foreignId('site_id')->constrained('sites');
|
||||
$table->unique(['project_id', 'site_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('project_site');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateLogsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->text('entry');
|
||||
$table->foreignId('user_id')->constrained('users');
|
||||
$table->morphs('parent'); // can be Site or Projet
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('logs');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateFilesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('files', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('uri', 2000)->nullable(); // max uri length is 2k
|
||||
$table->string('file')->nullable();
|
||||
$table->smallInteger('type');
|
||||
$table->smallInteger('category');
|
||||
$table->morphs('parent'); // can be Site or Projet
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('files');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreatePasswordResetsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('password_resets', function (Blueprint $table) {
|
||||
$table->string('email')->index();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('password_resets');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateWeekPlanningsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('week_plannings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->date('start');
|
||||
$table->text('planning');
|
||||
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('week_plannings');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Schedule;
|
||||
use App\Models\WeekPlanning;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateSchedulesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('schedules', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained('users');
|
||||
$table->date('date');
|
||||
$table->smallInteger('slot');
|
||||
$table->string('activity')->nullable();
|
||||
$table->text('description');
|
||||
$table->bigInteger('id_lie')->nullable();
|
||||
$table->string('model_lie')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['user_id','date','slot'], 'user_day_slot_unique');
|
||||
});
|
||||
|
||||
//migrate data from week plannings
|
||||
$week_plannings = WeekPlanning::all();
|
||||
|
||||
foreach ($week_plannings as $oneweek) {
|
||||
$user_id = $oneweek->user_id;
|
||||
$plan = $oneweek->planning;
|
||||
foreach ($plan as $time => $data) {
|
||||
$date = Carbon::createFromTimestamp($time);
|
||||
foreach ($data as $dayslot => $cell) {
|
||||
$activite = $this->convertColorToType($cell["type"]);
|
||||
$description = $cell["text"];
|
||||
$id_objetlie = $cell["foreign"];
|
||||
$type_objetlie = $cell["cellModel"]; //c'est une table d'après l'Enum CellModel
|
||||
|
||||
$collected = [
|
||||
'user_id' => $user_id,
|
||||
'date' => $date,
|
||||
'slot' => $dayslot,
|
||||
'activity' => $activite,
|
||||
'description' => $description,
|
||||
'id_lie' => $id_objetlie,
|
||||
'model_lie' => $type_objetlie
|
||||
];
|
||||
//si activite null et description null ne pas enregistrer
|
||||
if (!is_null($activite) && !is_null($description)) {
|
||||
$schedule = new Schedule();
|
||||
$schedule->fill($collected);
|
||||
$schedule->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('schedules');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* convertir une value de couleur en type
|
||||
*/
|
||||
private function convertColorToType($value) {
|
||||
$converter = array(
|
||||
'#c6e0b4' => 'support',
|
||||
'#b4c6e7' => 'rnd',
|
||||
'#00b0f0' => 'dev',
|
||||
'#ffe699' => 'projects',
|
||||
'#f4b084' => 'management',
|
||||
'#aeaaaa' => 'absence',
|
||||
'abscence' => 'absence'
|
||||
);
|
||||
return $converter[$value] ?? null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Schedule;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class MigrateSchedulesToEightSlotsPerDay extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
//migrate data from week plannings
|
||||
for ($i = 4; $i > 0; $i--) {
|
||||
$schedules = Schedule::where('slot', $i)->get();
|
||||
|
||||
foreach ($schedules as $oneschedule) {
|
||||
$newschedule = $oneschedule->replicate();
|
||||
$newschedule->slot = $i * 2;
|
||||
$newschedule->save();
|
||||
$oneschedule->slot = ($i * 2) - 1;
|
||||
$oneschedule->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//not reversible
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class DropWeekPlanningsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::dropIfExists('week_plannings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::create('week_plannings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->date('start');
|
||||
$table->text('planning');
|
||||
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Project;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateMarkersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('macro_markers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('label')->comment('Le libellé du marqueur.');
|
||||
$table->dateTime('marker')->comment('Le point dans le temps du marqueur.');
|
||||
|
||||
$table->foreignIdFor(Project::class)->constrained()
|
||||
->onUpdate('cascade')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('macro_markers');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Project;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateActivitiesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('macro_activities', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->dateTime('start')->comment('La date de début de l\'activité.');
|
||||
$table->dateTime('end')->comment('La date de fin de l\'activité.');
|
||||
$table->unsignedSmallInteger('activity')->comment('Enum MacroSchedule/Activity');
|
||||
$table->unsignedSmallInteger('load')->comment('Le nombre d\'hommes qui vont être mobilisé pour la tâche');
|
||||
$table->string('comment')->comment('Commentaire sur la tâche, (255 chars max)');
|
||||
|
||||
$table->foreignIdFor(Project::class)->constrained()
|
||||
->onUpdate('cascade')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('macro_activities');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Project;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ChangeStatusOnProjectsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Project::query()->update(['status' => '0']);
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
$table->unsignedSmallInteger('status')->default(0)
|
||||
->comment('Enum ProjectStatus')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
$table->string('status')->default('')->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
|
|||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIsInternalOnTicketsTable extends Migration
|
||||
class AssignUserToTickets extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
|
|
@ -14,7 +14,12 @@ class AddIsInternalOnTicketsTable extends Migration
|
|||
public function up()
|
||||
{
|
||||
Schema::table('tickets', function (Blueprint $table) {
|
||||
$table->tinyInteger('is_internal')->default(0)->after('status');
|
||||
$table->foreignId('assigned_to')
|
||||
->comment('La personne a qui est assigné le ticket.')
|
||||
->nullable()
|
||||
->after('created_by')
|
||||
->constrained('users')
|
||||
->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +31,8 @@ class AddIsInternalOnTicketsTable extends Migration
|
|||
public function down()
|
||||
{
|
||||
Schema::table('tickets', function (Blueprint $table) {
|
||||
$table->dropColumn('is_internal')->default(0);
|
||||
$table->dropForeign(['assigned_to']);
|
||||
$table->dropColumn('assigned_to');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
use App\Models\File as ModelsFile;
|
||||
use App\Models\Message;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class MoveImagesToStorage extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
Schema::table('storage', function (Blueprint $table) {
|
||||
if (File::exists(public_path('fichiers'))) {
|
||||
File::moveDirectory(public_path('fichiers'), public_path('storage/fichiers'));
|
||||
Ticket::all()->map(function (Ticket $ticket) {
|
||||
if ($ticket->file) {
|
||||
$ticket->file = "storage/{$ticket->file}";
|
||||
}
|
||||
if ($ticket->file2) {
|
||||
$ticket->file2 = "storage/{$ticket->file2}";
|
||||
}
|
||||
$ticket->saveQuietly();
|
||||
});
|
||||
Message::all()->map(function (Message $message) {
|
||||
if ($message->file) {
|
||||
$message->file = "storage/{$message->file}";
|
||||
}
|
||||
$message->saveQuietly();
|
||||
});
|
||||
ModelsFile::all()->map(function (ModelsFile $fichier) {
|
||||
if ($fichier->file) {
|
||||
$fichier->file = "storage/{$fichier->file}";
|
||||
}
|
||||
$fichier->saveQuietly();
|
||||
});
|
||||
} //end if
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('storage', function (Blueprint $table) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Label;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateLabelsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('labels', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->unique()->comment('Un ou deux mot désignant le label');
|
||||
$table->unsignedInteger('color')->comment('Cast HTML color');
|
||||
});
|
||||
|
||||
$defaultLabels = [
|
||||
['name' => 'urgent', 'color' => '#ebb734'],
|
||||
['name' => 'bloquant', 'color' => '#851400']
|
||||
];
|
||||
collect($defaultLabels)->map(function (array $defaultLabel) {
|
||||
$label = new Label();
|
||||
$label->name = $defaultLabel['name'];
|
||||
$label->color = $defaultLabel['color'];
|
||||
$label->saveOrFail();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('labels');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Label;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateLabelTicketTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('label_ticket', function (Blueprint $table) {
|
||||
$table->foreignIdFor(Ticket::class)
|
||||
->constrained()->cascadeOnDelete();
|
||||
$table->foreignIdFor(Label::class)
|
||||
->constrained()->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('label_ticket');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue