Estimation functionnality
This commit is contained in:
parent
95fc7c797b
commit
3dc367ccde
61 changed files with 2370 additions and 77 deletions
|
|
@ -1,3 +1,6 @@
|
|||
### 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
|
||||
|
|
|
|||
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 [
|
||||
|
|
|
|||
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());
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -38,12 +38,7 @@ class Kernel extends HttpKernel
|
|||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\SecureHeaders::class,
|
||||
'xss'
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
117
app/Models/Brand.php
Normal file
117
app/Models/Brand.php
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?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(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update()
|
||||
]
|
||||
],
|
||||
];
|
||||
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';
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ class Site extends Model
|
|||
]
|
||||
],
|
||||
'projects' => [
|
||||
'label' => trans('Projets'),
|
||||
'label' => trans('models.classes.projects'),
|
||||
'type' => CrudType::belongsToMany(),
|
||||
'belongsToMany' => Project::class,
|
||||
'belongsToManyLabel' => 'name',
|
||||
|
|
@ -205,6 +205,16 @@ class Site extends Model
|
|||
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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ 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;
|
||||
|
|
@ -89,6 +91,16 @@ 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
|
||||
*
|
||||
|
|
@ -203,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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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)
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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,7 +7,7 @@ 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
|
||||
|
|
@ -33,4 +33,52 @@ abstract class TicketStaticRules extends StaticRules
|
|||
{
|
||||
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)
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -57,7 +57,21 @@ class TicketPolicy extends RootPolicy
|
|||
TicketStaticRules::make('ticketIsNotArchived', $model)
|
||||
]),
|
||||
'assignTickets' => URR::make('userIsAdmin', $user),
|
||||
'assignLabels' => 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,6 +2,8 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Brand;
|
||||
use App\Models\Credit\Account;
|
||||
use App\Models\File;
|
||||
use App\Models\Label;
|
||||
use App\Models\Log;
|
||||
|
|
@ -39,15 +41,12 @@ class RouteServiceProvider extends ServiceProvider
|
|||
$this->cspReportRoute();
|
||||
|
||||
$this->routes(function () {
|
||||
Route::prefix('api')
|
||||
->middleware('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/api.php'));
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ldcweb/supportweb",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"type": "project",
|
||||
"description": "The Laravel Framework.",
|
||||
"keywords": [
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"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",
|
||||
|
|
|
|||
14
composer.lock
generated
14
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": "a9e301f0bab428769de214ee3b71cd46",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
];
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateBrandsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('brands', function (Blueprint $table) {
|
||||
$table->id()->comment('Doit correspondre aux ids marques de la CRM');
|
||||
|
||||
$table->string('name')->comment('Nom de la marque');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('brands');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Brand;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddBrandIdToSites extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('sites', function (Blueprint $table) {
|
||||
$table->foreignIdFor(Brand::class)
|
||||
->nullable()
|
||||
->comment('Relation is nullable, since not all site would be affiliate with a brand.')
|
||||
->after('todo')
|
||||
->constrained();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('sites', function (Blueprint $table) {
|
||||
$table->dropForeign(['brand_id']);
|
||||
$table->dropColumn('brand_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Brand;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateCreditAccountsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('credit_accounts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedTinyInteger('type')->comment('Enum Credit\\AccountType');
|
||||
|
||||
$table->string('label')->comment('Custom account label');
|
||||
$table->float('balance')->comment('Account balance');
|
||||
|
||||
$table->foreignIdFor(Brand::class)
|
||||
->constrained();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('credit_accounts');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateCreditLogsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('credit_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedTinyInteger('type')->comment('Enum Credit\\BalanceOperation');
|
||||
$table->unsignedFloat('amount')->comment('Montant de l\'opération');
|
||||
|
||||
$table->foreignId('credit_account_id')
|
||||
->constrained('credit_accounts')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->timestamp('created_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('credit_logs');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Credit\EstimationStatus;
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateCreditEstimationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('credit_estimations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedTinyInteger('status')->default(EstimationStatus::pending())
|
||||
->comment('Enum Credit\\EstimationStatus');
|
||||
|
||||
$table->unsignedFloat('amount')->comment('Estimation amount');
|
||||
|
||||
$table->foreignIdFor(Ticket::class)
|
||||
->constrained();
|
||||
|
||||
$table->foreignId('credit_account_id')
|
||||
->comment('The account to debit.')
|
||||
->constrained('credit_accounts');
|
||||
$table->foreignId('estimator_id')
|
||||
->comment('The person who has done the estimate.')
|
||||
->constrained('users');
|
||||
$table->foreignId('validator_id')
|
||||
->nullable()
|
||||
->comment('The person who has validate the estimate.')
|
||||
->constrained('users');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('credit_estimations');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateCreditEstimationLogTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('credit_estimation_log', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->foreignId('credit_log_id')
|
||||
->constrained('credit_logs')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->foreignId('credit_estimation_id')
|
||||
->constrained('credit_estimations')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('credit_estimation_log');
|
||||
}
|
||||
}
|
||||
8
resources/js/bootstrap.js
vendored
8
resources/js/bootstrap.js
vendored
|
|
@ -20,8 +20,12 @@ window._ = require('lodash')
|
|||
|
||||
window.axios = require('axios')
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
|
||||
let csrfToken = document.querySelector('meta[name="csrf-token"]')
|
||||
if (csrfToken.getAttribute('content')) {
|
||||
window.axios.defaults.headers.common['Authorization'] = `Bearer ${csrfToken.getAttribute('content')}`
|
||||
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken.getAttribute('content')
|
||||
}
|
||||
|
||||
|
||||
require('fslightbox')
|
||||
|
|
|
|||
|
|
@ -2,10 +2,15 @@
|
|||
|
||||
return [
|
||||
'classes' => [
|
||||
'users' => 'Users',
|
||||
'accounts' => 'Accounts',
|
||||
'projects' => 'Projects',
|
||||
'markers' => 'Markers',
|
||||
'tickets' => 'Tickets',
|
||||
'logs' => 'Logs',
|
||||
'creditlogs' => 'Logs',
|
||||
'files' => 'Files',
|
||||
'sites' => 'Sites',
|
||||
'brands' => 'Brands',
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,10 +2,15 @@
|
|||
|
||||
return [
|
||||
'classes' => [
|
||||
'users' => 'Utilisateurs',
|
||||
'accounts' => 'Comptes',
|
||||
'projects' => 'Projets',
|
||||
'markers' => 'Marqueurs',
|
||||
'tickets' => 'Tickets',
|
||||
'logs' => 'Journaux',
|
||||
'creditlogs' => 'Historique',
|
||||
'files' => 'Fichiers',
|
||||
'sites' => 'Sites',
|
||||
'brands' => 'Marques',
|
||||
]
|
||||
];
|
||||
|
|
|
|||
33
resources/views/accounts/credit.blade.php
Normal file
33
resources/views/accounts/credit.blade.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('bodyClass', 'col-md-8')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="col h4">
|
||||
<a href="{{ route('brands.credit_accounts.index', $brand) }}" class="text-decoration-none btn btn-sm btn-primary" role="button" aria-roledescription="{{ _('Revenir en arrière') }}"><i class="fa-solid fa-arrow-left-long"></i></a>
|
||||
{{ $brand->name }} :
|
||||
{{ $account->label }}
|
||||
<span class="text-decoration-underline">{{ __('Créditer un compte') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('brands.credit_accounts.creditSubmit', ['brand' => $brand, 'account' => $account]) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="form-group mb-5">
|
||||
<label for="amount" class="col-form-label text-md-right">{{ __('Montant crédité') }}</label>
|
||||
<input class="form-control" name="amount" id="amount" type="number" min="0.5" step="0.5" value="{{ old('number', 0.5) }}" aria-describedby="amountHelper"/>
|
||||
@include('modules.input-error', [
|
||||
'inputName' => 'amount', 'helperId' => 'amountHelper' , 'helper' => __('Le montant en homme jour à créditer')
|
||||
])
|
||||
</div>
|
||||
|
||||
<div class="form-group text-center">
|
||||
<input type="submit" class="btn btn-warning" value="Créditer">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
121
resources/views/accounts/index.blade.php
Normal file
121
resources/views/accounts/index.blade.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
@extends($viewLayout)
|
||||
|
||||
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.viewAny')))
|
||||
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), trans(Str::plural(class_basename($modelClass)))))
|
||||
@section('metaIndex', 'noindex,nofollow')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
@include('crud-policies::crud.modules.flashMessage')
|
||||
<div class="card-header">
|
||||
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
|
||||
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col">
|
||||
{{ $models->links() }}
|
||||
</div>
|
||||
<div class="col">
|
||||
<form action="{{ url()->current() }}">
|
||||
<div class="input-group mb-3">
|
||||
<input type="search" name="search" class="form-control" value="{{ request()->search }}"/>
|
||||
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}"/>
|
||||
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}"/>
|
||||
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset') }}</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ __('crud-policies::crud.search') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table text-center">
|
||||
<thead class="thead-dark">
|
||||
@include('crud-policies::crud.modules.sorter')
|
||||
</thead>
|
||||
<tbody>
|
||||
@if(count($models))
|
||||
|
||||
{{-- INIT QUERIES for OPTIMIZING --}}
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if($prop['type']->equals(CrudType::order()))
|
||||
@php ${"$modelTable$fieldName"} = $modelClass::max($fieldName); @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::belongsTo()))
|
||||
@php try { $models->load(Str::singular((new $prop['belongsTo']())->getTable())); } catch(\Exception $e) {} @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::belongsToMany()))
|
||||
@php $models->load("$fieldName"); @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::hasMany()))
|
||||
@php $models->load("$fieldName"); @endphp
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- DISPLAY MODELS --}}
|
||||
@foreach($models as $model)
|
||||
<tr>
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if(in_array(CrudAction::viewAny(), $prop['actions']))
|
||||
@switch($prop['type'])
|
||||
@case(CrudType::password())
|
||||
{{-- Ignore --}}
|
||||
@break
|
||||
@default
|
||||
<td>@include('crud-policies::crud.printfield', [
|
||||
'action' => CrudAction::viewAny(),
|
||||
'field' => $model->{$fieldName},
|
||||
'props' => $prop,
|
||||
"$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null
|
||||
])</td>
|
||||
@endswitch
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- MODEL ACTIONS --}}
|
||||
<td>
|
||||
<div class="btn-group" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
|
||||
@can('viewAny', \App\Models\Credit\CreditLog::class)
|
||||
@if(is_object(request()->brand) and get_class(request()->brand) === App\Models\Brand::class)
|
||||
<a href="{{ CrudController::getRoutePrefixed('credit_logs.index', ['brand' => request()->brand, 'account' => $model]) }}" class="btn btn-sm btn-dark">{{ __('Historique') }}</a>
|
||||
@else
|
||||
<a href="{{ CrudController::getRoutePrefixed('credit_accounts.credit_logs.index', $model) }}" class="btn btn-sm btn-dark">{{ __('Historique') }}</a>
|
||||
@endif
|
||||
@endcan
|
||||
@can('credit', $model)
|
||||
@if(is_object(request()->brand) and get_class(request()->brand) === App\Models\Brand::class)
|
||||
<a href="{{ route('brands.credit_accounts.credit', ['brand' => request()->brand, 'account' => $model]) }}" class="btn btn-sm btn-warning">{{ __('Créditer') }}</a>
|
||||
@endif
|
||||
@endcan
|
||||
@can('view', $model)
|
||||
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}" class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
|
||||
@endcan
|
||||
@can('update', $model)
|
||||
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model) }}" class="btn btn-sm btn-info">{{ __('crud-policies::crud.edit') }}</a>
|
||||
@endcan
|
||||
@can('delete', $model)
|
||||
<form action="{{ CrudController::getRoutePrefixed("$modelTable.destroy", $model) }}" method="POST" onsubmit="__CRUD.confirmDelete(event)">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">{{ __('crud-policies::crud.delete') }}</button>
|
||||
</form>
|
||||
@endcan
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@else
|
||||
<tr>
|
||||
<td colspan="{{ count($modelClass::getEditableProperties()) }}">
|
||||
{{ __('crud-policies::crud.nothingtoshow') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
{{ $models->links() }}
|
||||
</div>
|
||||
</div>
|
||||
@include('crud-policies::crud.modules.scriptConstants')
|
||||
@endsection
|
||||
112
resources/views/brands/index.blade.php
Normal file
112
resources/views/brands/index.blade.php
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
@extends($viewLayout)
|
||||
|
||||
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.viewAny')))
|
||||
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), trans(Str::plural(class_basename($modelClass)))))
|
||||
@section('metaIndex', 'noindex,nofollow')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
@include('crud-policies::crud.modules.flashMessage')
|
||||
<div class="card-header">
|
||||
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
|
||||
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col">
|
||||
{{ $models->links() }}
|
||||
</div>
|
||||
<div class="col">
|
||||
<form action="{{ url()->current() }}">
|
||||
<div class="input-group mb-3">
|
||||
<input type="search" name="search" class="form-control" value="{{ request()->search }}"/>
|
||||
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}"/>
|
||||
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}"/>
|
||||
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset') }}</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ __('crud-policies::crud.search') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table text-center">
|
||||
<thead class="thead-dark">
|
||||
@include('crud-policies::crud.modules.sorter')
|
||||
</thead>
|
||||
<tbody>
|
||||
@if(count($models))
|
||||
|
||||
{{-- INIT QUERIES for OPTIMIZING --}}
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if($prop['type']->equals(CrudType::order()))
|
||||
@php ${"$modelTable$fieldName"} = $modelClass::max($fieldName); @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::belongsTo()))
|
||||
@php try { $models->load(Str::singular((new $prop['belongsTo']())->getTable())); } catch(\Exception $e) {} @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::belongsToMany()))
|
||||
@php $models->load("$fieldName"); @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::hasMany()))
|
||||
@php $models->load("$fieldName"); @endphp
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- DISPLAY MODELS --}}
|
||||
@foreach($models as $model)
|
||||
<tr>
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if(in_array(CrudAction::viewAny(), $prop['actions']))
|
||||
@switch($prop['type'])
|
||||
@case(CrudType::password())
|
||||
{{-- Ignore --}}
|
||||
@break
|
||||
@default
|
||||
<td>@include('crud-policies::crud.printfield', [
|
||||
'action' => CrudAction::viewAny(),
|
||||
'field' => $model->{$fieldName},
|
||||
'props' => $prop,
|
||||
"$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null
|
||||
])</td>
|
||||
@endswitch
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- MODEL ACTIONS --}}
|
||||
<td>
|
||||
<div class="btn-group" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
|
||||
@can('viewAny', \App\Models\Credit\Account::class)
|
||||
<a href="{{ CrudController::getRoutePrefixed("brands.credit_accounts.index", $model) }}" class="btn btn-sm btn-dark">{{ __('models.classes.accounts') }}</a>
|
||||
@endcan
|
||||
@can('view', $model)
|
||||
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}" class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
|
||||
@endcan
|
||||
@can('update', $model)
|
||||
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model) }}" class="btn btn-sm btn-info">{{ __('crud-policies::crud.edit') }}</a>
|
||||
@endcan
|
||||
@can('delete', $model)
|
||||
<form action="{{ CrudController::getRoutePrefixed("$modelTable.destroy", $model) }}" method="POST" onsubmit="__CRUD.confirmDelete(event)">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">{{ __('crud-policies::crud.delete') }}</button>
|
||||
</form>
|
||||
@endcan
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@else
|
||||
<tr>
|
||||
<td colspan="{{ count($modelClass::getEditableProperties()) }}">
|
||||
{{ __('crud-policies::crud.nothingtoshow') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
{{ $models->links() }}
|
||||
</div>
|
||||
</div>
|
||||
@include('crud-policies::crud.modules.scriptConstants')
|
||||
@endsection
|
||||
117
resources/views/credit_logs/index.blade.php
Normal file
117
resources/views/credit_logs/index.blade.php
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
@extends($viewLayout)
|
||||
|
||||
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.viewAny')))
|
||||
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), trans(Str::plural(class_basename($modelClass)))))
|
||||
@section('metaIndex', 'noindex,nofollow')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
@include('crud-policies::crud.modules.flashMessage')
|
||||
<div class="card-header">
|
||||
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
|
||||
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col">
|
||||
{{ $models->links() }}
|
||||
</div>
|
||||
<div class="col">
|
||||
<form action="{{ url()->current() }}">
|
||||
<div class="input-group mb-3">
|
||||
<input type="search" name="search" class="form-control" value="{{ request()->search }}"/>
|
||||
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}"/>
|
||||
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}"/>
|
||||
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset') }}</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ __('crud-policies::crud.search') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table text-center">
|
||||
<thead class="thead-dark">
|
||||
@include('crud-policies::crud.modules.sorter')
|
||||
</thead>
|
||||
<tbody>
|
||||
@if(count($models))
|
||||
|
||||
{{-- INIT QUERIES for OPTIMIZING --}}
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if($prop['type']->equals(CrudType::order()))
|
||||
@php ${"$modelTable$fieldName"} = $modelClass::max($fieldName); @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::belongsTo()))
|
||||
@php try { $models->load(Str::singular((new $prop['belongsTo']())->getTable())); } catch(\Exception $e) {} @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::belongsToMany()))
|
||||
@php $models->load("$fieldName"); @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::hasMany()))
|
||||
@php $models->load("$fieldName"); @endphp
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- DISPLAY MODELS --}}
|
||||
@foreach($models as $model)
|
||||
<tr>
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if(in_array(CrudAction::viewAny(), $prop['actions']))
|
||||
@if($fieldName === 'amount')
|
||||
<td><span class="fw-bold">{{ __(':amount jour(s)/homme', ['amount' => number_format($model->{$fieldName}, 1, ',', '')]) }}</span></td>
|
||||
@continue
|
||||
@endif
|
||||
@if($fieldName === 'type')
|
||||
<td><span class="fw-bold @if($model->{$fieldName}->equals(App\Enums\Credit\BalanceOperation::credit())) text-success @else text-danger @endif">{{ $model->{$fieldName}->label }}</span></td>
|
||||
@continue
|
||||
@endif
|
||||
@switch($prop['type'])
|
||||
@case(CrudType::password())
|
||||
{{-- Ignore --}}
|
||||
@break
|
||||
@default
|
||||
<td>@include('crud-policies::crud.printfield', [
|
||||
'action' => CrudAction::viewAny(),
|
||||
'field' => $model->{$fieldName},
|
||||
'props' => $prop,
|
||||
"$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null
|
||||
])</td>
|
||||
@endswitch
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- MODEL ACTIONS --}}
|
||||
<td>
|
||||
<div class="btn-group" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
|
||||
@can('view', $model)
|
||||
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}" class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
|
||||
@endcan
|
||||
@can('update', $model)
|
||||
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model) }}" class="btn btn-sm btn-info">{{ __('crud-policies::crud.edit') }}</a>
|
||||
@endcan
|
||||
@can('delete', $model)
|
||||
<form action="{{ CrudController::getRoutePrefixed("$modelTable.destroy", $model) }}" method="POST" onsubmit="__CRUD.confirmDelete(event)">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">{{ __('crud-policies::crud.delete') }}</button>
|
||||
</form>
|
||||
@endcan
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@else
|
||||
<tr>
|
||||
<td colspan="{{ count($modelClass::getEditableProperties()) }}">
|
||||
{{ __('crud-policies::crud.nothingtoshow') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
{{ $models->links() }}
|
||||
</div>
|
||||
</div>
|
||||
@include('crud-policies::crud.modules.scriptConstants')
|
||||
@endsection
|
||||
25
resources/views/estimations/create.blade.php
Normal file
25
resources/views/estimations/create.blade.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('bodyClass', 'col-md-8')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="col h4">
|
||||
<a href="{{ route('ticket.show', $ticket) }}" class="text-decoration-none btn btn-sm btn-primary" role="button" aria-roledescription="{{ _('Revenir en arrière') }}"><i class="fa-solid fa-arrow-left-long"></i></a>
|
||||
{{ $ticket->full_name }}
|
||||
@if ($ticket->is_internal)<i class="fa-solid fa-lock"></i>@endif
|
||||
<span class="text-decoration-underline">{{ __('Créer une estimation') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('ticket.estimation.estimateSubmit', $ticket) }}">
|
||||
@csrf
|
||||
@include('estimations.form-inputs')
|
||||
<div class="form-group text-center">
|
||||
<input type="submit" class="btn btn-warning" value="Valider">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
19
resources/views/estimations/form-inputs.blade.php
Normal file
19
resources/views/estimations/form-inputs.blade.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<div class="form-group mb-5">
|
||||
<label for="amount" class="col-form-label text-md-right">{{ __('Montant estimé') }}</label>
|
||||
<input class="form-control" name="amount" id="amount" type="number" min="0.5" step="0.5" value="{{ old('number', 0.5) }}" aria-describedby="amountHelper"/>
|
||||
@include('modules.input-error', [
|
||||
'inputName' => 'amount', 'helperId' => 'amountHelper' , 'helper' => __('Le montant en homme jour estimé.')
|
||||
])
|
||||
</div>
|
||||
<div class="form-group mb-5">
|
||||
<label for="creditAccount"><b>{{ __('Compte à débiter') }}</b></label>
|
||||
<select class="form-select" name="credit_account_id" id="creditAccount" aria-describedby="creditAccountHelp">
|
||||
<option value="" @if(!old('credit_account_id')) selected @endif disabled>{{ __('Veuillez sélectionner un compte à débiter') }}</option>
|
||||
@foreach($ticket->site->brand->accounts as $account)
|
||||
<option value="{{ $account->id }}" @if(old('credit_account_id') == $account->id) selected @endif>{{ $account->label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@include('modules.input-error', [
|
||||
'inputName' => 'brand_id', 'helperId' => 'creditAccountHelp' , 'helper' => __('Le compte de la marque à débiter')
|
||||
])
|
||||
</div>
|
||||
45
resources/views/estimations/pendingCard.blade.php
Normal file
45
resources/views/estimations/pendingCard.blade.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="h5">{{ __('Estimation faite par :user', ['user' => $estimation->estimator->name]) }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<span class="fw-bold h4">{{ __('Temps estimé :time jours/homme', ['time' => $estimation->amount]) }}</span>
|
||||
</div>
|
||||
@if($estimation->status->equals(App\Enums\Credit\EstimationStatus::pending()))
|
||||
<div class="col d-flex justify-content-end">
|
||||
<span class="h4 text-dark">{{ __('En attente') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if($estimation->status->equals(App\Enums\Credit\EstimationStatus::accepted()))
|
||||
<div class="col d-flex justify-content-end">
|
||||
<span class="h4 text-success">{{ __('Validé par :user', ['user' => optional($estimation->validator)->name ?? 'personne supprimée']) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if($estimation->status->equals(App\Enums\Credit\EstimationStatus::refused()))
|
||||
<div class="col d-flex justify-content-end">
|
||||
<span class="h4 text-danger">{{ __('Refusé par :user', ['user' => optional($estimation->validator)->name ?? 'personne supprimée']) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@can('validateEstimate', $ticket)
|
||||
@can('answer', $estimation)
|
||||
<div class="col d-flex justify-content-end">
|
||||
<form class="mx-1" action="{{ route('ticket.estimation.estimatePendingSubmit', $ticket) }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="status" value="accept">
|
||||
<button type="submit" class="btn btn-primary">{{ __('J\'accepte') }}</button>
|
||||
</form>
|
||||
<form class="mx-1" action="{{ route('ticket.estimation.estimatePendingSubmit', $ticket) }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="status" value="refuse">
|
||||
<button type="submit" class="btn btn-warning">{{ __('Je refuse') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
@endcan
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -13,24 +13,44 @@
|
|||
<ul class="navbar-nav me-auto">
|
||||
@can('viewAny', App\Models\Ticket::class)
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(strpos(request()->route()->getName(), 'ticket') === 0)active @endif" href="{{ route('ticket.index') }}">Tickets</a>
|
||||
<a class="nav-link @if(strpos(request()->route()->getName(), 'ticket') === 0)active @endif" href="{{ route('ticket.index') }}">{{ __('models.classes.tickets') }}</a>
|
||||
</li>
|
||||
@endcan
|
||||
@can('viewAny', App\Models\Site::class)
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(strpos(request()->route()->getName(), 'sites') === 0)active @endif" href="{{ route('sites.index') }}">Sites</a>
|
||||
@if(
|
||||
auth()->user()->can('viewAny', \App\Models\Brand::class) or
|
||||
auth()->user()->can('viewAny', \App\Models\Site::class) or
|
||||
auth()->user()->can('viewAny', \App\Models\Project::class)
|
||||
)
|
||||
<li class="nav-item dropdown user-dropdown">
|
||||
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ __('Gestion') }} <span class="caret"></span>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||
@can('viewAny', App\Models\Brand::class)
|
||||
<a class="dropdown-item @if(strpos(request()->route()->getName(), 'brands') === 0)active @endif" href="{{ route('brands.index') }}">{{ __('models.classes.brands') }}</a>
|
||||
@endcan
|
||||
@can('viewAny', App\Models\Site::class)
|
||||
<a class="dropdown-item @if(strpos(request()->route()->getName(), 'sites') === 0)active @endif" href="{{ route('sites.index') }}">{{ __('models.classes.sites') }}</a>
|
||||
@endcan
|
||||
@can('viewAny', App\Models\Project::class)
|
||||
<a class="dropdown-item @if(strpos(request()->route()->getName(), 'projects') === 0)active @endif" href="{{ route('projects.index') }}">{{ __('models.classes.projects') }}</a>
|
||||
@endcan
|
||||
</div>
|
||||
</li>
|
||||
@endcan
|
||||
@can('viewAny', App\Models\Project::class)
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(strpos(request()->route()->getName(), 'projects') === 0)active @endif" href="{{ route('projects.index') }}">Projets</a>
|
||||
</li>
|
||||
@endcan
|
||||
@endif
|
||||
@can('viewAny', App\Models\User::class)
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(strpos(request()->route()->getName(), 'users') === 0)active @endif" href="{{ route('users.index') }}">Utilisateurs</a>
|
||||
<a class="nav-link @if(strpos(request()->route()->getName(), 'users') === 0)active @endif" href="{{ route('users.index') }}">{{ __('models.classes.users') }}</a>
|
||||
</li>
|
||||
@endcan
|
||||
@if(!Auth::user()->isAdmin())
|
||||
@can('viewAny', App\Models\Credit\Account::class)
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(strpos(request()->route()->getName(), 'users') === 0)active @endif" href="{{ route('credit_accounts.index') }}">{{ __('models.classes.accounts') }}</a>
|
||||
</li>
|
||||
@endcan
|
||||
@endif
|
||||
@if(Auth::user()->isAdmin())
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(strpos(request()->route()->getName(), 'schedule') === 0)active @endif" href="{{ route('schedule.index') }}">{{ __('Planning') }}</a>
|
||||
|
|
@ -42,7 +62,7 @@
|
|||
</ul>
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<span class="nav-link text-info fw-bold">{{ __('Tickets') }}</span>
|
||||
<span class="nav-link text-info fw-bold">{{ __('models.classes.tickets') }}</span>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<span class="nav-link text-light">{{ __('Nouveau') }} : <span class="text-info">{{ $ticketsNew ?? 0 }}</span></span>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
@if(request()->project)
|
||||
<a href="{{ route('projects.show', request()->project) }}" class="btn btn-success float-end">{{ __('Projet') }}</a>
|
||||
<a href="{{ route('projects.macro_markers.index', request()->project) }}" class="btn btn-warning float-end">{{ __('models.classes.markers') }}</a>
|
||||
<a href="{{ route('projects.macro_markers.index', request()->project) }}" class="btn btn-warning float-end">{{ Str::singular(__('models.classes.markers')) }}</a>
|
||||
<a href="{{ route('projects.logs.index', request()->project) }}" class="btn btn-dark float-end">{{ __('models.classes.logs') }}</a>
|
||||
<a href="{{ route('projects.files.index', request()->project) }}" class="btn btn-dark float-end">{{ __('models.classes.files') }}</a>
|
||||
@endif
|
||||
@if(request()->site)
|
||||
<a href="{{ route('sites.show', request()->site) }}" class="btn btn-success float-end">{{ __('models.classes.sites') }}</a>
|
||||
<a href="{{ route('sites.show', request()->site) }}" class="btn btn-success float-end">{{ Str::singular(__('models.classes.sites')) }}</a>
|
||||
<a href="{{ route('sites.logs.index', request()->site) }}" class="btn btn-dark float-end">{{ __('models.classes.logs') }}</a>
|
||||
<a href="{{ route('sites.files.index', request()->site) }}" class="btn btn-dark float-end">{{ __('models.classes.files') }}</a>
|
||||
@endif
|
||||
@if(request()->brand)
|
||||
<a href="{{ route('brands.show', request()->brand) }}" class="btn btn-success float-end">{{ Str::singular(__('models.classes.brands')) }}</a>
|
||||
<a href="{{ route('brands.credit_accounts.index', request()->brand) }}" class="btn btn-dark float-end">{{ __('models.classes.accounts') }}</a>
|
||||
@endif
|
||||
|
|
|
|||
8
resources/views/modules/input-error.blade.php
Normal file
8
resources/views/modules/input-error.blade.php
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
@if($errors->has($inputName))
|
||||
<div class="col-12">
|
||||
<small class="text-danger">{{ implode(',', $errors->get($inputName)) }}</small>
|
||||
</div>
|
||||
@endif
|
||||
@if(isset($helper))
|
||||
<small @if(isset($helperId)) id="{{ $helperId }}" @endif class="form-text text-muted">{{ $helper }}</small>
|
||||
@endif
|
||||
|
|
@ -6,11 +6,7 @@
|
|||
<div class="form-row row">
|
||||
<h2 class="col">
|
||||
<a href="{{ route('ticket.index') }}" class="text-decoration-none btn btn-sm btn-primary" role="button" aria-roledescription="{{ _('Revenir en arrière') }}"><i class="fa-solid fa-arrow-left-long"></i></a>
|
||||
{{ __('Ticket n° :ticketId : :ticketSiteName - :ticketTypeLabel', [
|
||||
'ticketId' => $ticket->id,
|
||||
'ticketSiteName' => $ticket->site->name,
|
||||
'ticketTypeLabel' => $ticket->type->label
|
||||
]) }}
|
||||
{{ $ticket->full_name }}
|
||||
@if ($ticket->is_internal)<i class="fa-solid fa-lock"></i>@endif
|
||||
<span class="badge bg-info">{{ $ticket->status->label }}</span>
|
||||
</h2>
|
||||
|
|
@ -71,11 +67,15 @@
|
|||
</div>
|
||||
@endcan
|
||||
<hr>
|
||||
@if($ticket->estimation)
|
||||
@include('estimations.pendingCard', ['ticket' => $ticket, 'estimation' => $ticket->estimation])
|
||||
<hr>
|
||||
@endif
|
||||
<div>
|
||||
<h4>
|
||||
{{ __('Suivi') }}
|
||||
@can('createMessage', $ticket)
|
||||
<button type="button" class="btn btn-sm btn-primary float-end" data-bs-toggle="modal" data-bs-target="#messageModal">
|
||||
<button type="button" class="btn btn-sm btn-primary mx-1 float-end" data-bs-toggle="modal" data-bs-target="#messageModal">
|
||||
{{ __('Répondre') }}
|
||||
</button>
|
||||
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
|
||||
|
|
@ -121,6 +121,9 @@
|
|||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@can('estimate', $ticket)
|
||||
<a href="{{ route('ticket.estimation.estimate', $ticket) }}" class="btn btn-sm btn-info mx-1 float-end">{{ __('Estimation') }}</a>
|
||||
@endcan
|
||||
</h4>
|
||||
@foreach ($messages as $message)
|
||||
@if ($message->user_id === null)
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
<?php
|
||||
|
|
@ -1 +0,0 @@
|
|||
<?php
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\BrandController;
|
||||
use App\Http\Controllers\Credit\AccountController;
|
||||
use App\Http\Controllers\Credit\CreditLogController;
|
||||
use App\Http\Controllers\Credit\EstimationController;
|
||||
use App\Http\Controllers\FileController;
|
||||
use App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\LabelController;
|
||||
|
|
@ -50,7 +54,7 @@ Route::get('/ping', function () {
|
|||
return auth()->check() ? response()->json() : response()->json([], 401);
|
||||
})->withoutMiddleware('auth');
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::middleware('auth:web')->group(function () {
|
||||
|
||||
// * Macro planning
|
||||
Route::get('/macroplanning', [ScheduleMacroController::class, 'show'])->name('macroplanning.show');
|
||||
|
|
@ -66,10 +70,8 @@ Route::middleware(['auth'])->group(function () {
|
|||
// * Resources
|
||||
Route::resource('ticket', TicketController::class)->except(['destroy']);
|
||||
Route::resource('users', UserController::class);
|
||||
Route::resource(
|
||||
'labels',
|
||||
LabelController::class
|
||||
)->except("show");
|
||||
Route::resource('labels', LabelController::class)->except('show');
|
||||
Route::resource('brands', BrandController::class);
|
||||
Route::resource('sites', SiteController::class)
|
||||
->withoutMiddleware('xss')
|
||||
->middleware('xss:description,todo');
|
||||
|
|
@ -81,9 +83,34 @@ Route::middleware(['auth'])->group(function () {
|
|||
'macro_markers' => 'marker'
|
||||
]);
|
||||
|
||||
// * Brands and Accounts
|
||||
Route::resource('credit_accounts', AccountController::class)->only(['index'])
|
||||
->parameters([
|
||||
'credit_accounts' => 'account'
|
||||
]);
|
||||
Route::resource('credit_accounts.credit_logs', CreditLogController::class)->only(['index'])
|
||||
->parameters([
|
||||
'credit_accounts' => 'account',
|
||||
'credit_logs' => 'credit_log'
|
||||
]);
|
||||
Route::resource('brands.credit_accounts', AccountController::class)
|
||||
->parameters([
|
||||
'credit_accounts' => 'account'
|
||||
]);
|
||||
Route::get('/brand/{brand}/credit_accounts/{account}/credit', [AccountController::class, 'credit'])
|
||||
->name('brands.credit_accounts.credit');
|
||||
Route::put('/brand/{brand}/credit_accounts/{account}/credit', [AccountController::class, 'creditSubmit'])
|
||||
->name('brands.credit_accounts.creditSubmit');
|
||||
Route::resource('brands.credit_accounts.credit_logs', CreditLogController::class)
|
||||
->only(['index'])
|
||||
->parameters([
|
||||
'credit_accounts' => 'account',
|
||||
'credit_logs' => 'credit_log'
|
||||
]);
|
||||
|
||||
// * LOGS and Files
|
||||
Route::resource('logs', LogController::class);
|
||||
Route::resource('files', FileController::class);
|
||||
Route::resource('logs', LogController::class)->only('index');
|
||||
Route::resource('files', FileController::class)->only('index');
|
||||
Route::resource('projects.logs', LogController::class);
|
||||
Route::resource('projects.files', FileController::class);
|
||||
Route::resource('sites.logs', LogController::class);
|
||||
|
|
@ -91,9 +118,24 @@ Route::middleware(['auth'])->group(function () {
|
|||
|
||||
// * Ticket
|
||||
Route::prefix('/ticket')->name('ticket.')->group(function () {
|
||||
|
||||
// * Estimation
|
||||
Route::name('estimation.')->group(function () {
|
||||
Route::get('/{ticket}/estimate', [EstimationController::class, 'estimate'])
|
||||
->middleware('can:estimate,ticket')
|
||||
->name('estimate');
|
||||
Route::post('/{ticket}/estimate', [EstimationController::class, 'estimateSubmit'])
|
||||
->middleware('can:estimate,ticket')
|
||||
->name('estimateSubmit');
|
||||
Route::post('/{ticket}/estimate/answer', [EstimationController::class, 'estimatePendingSubmit'])
|
||||
->middleware('can:validateEstimate,ticket')
|
||||
->name('estimatePendingSubmit');
|
||||
});
|
||||
|
||||
|
||||
Route::post('/{ticket}/assignTo', [TicketController::class, 'assignTo'])
|
||||
->middleware('can:assignTo,ticket')
|
||||
->name('assignTo');
|
||||
->middleware('can:assignTo,ticket')
|
||||
->name('assignTo');
|
||||
Route::post('/{ticket}/reopen', [TicketController::class, 'reopen'])
|
||||
->middleware('can:reopen,ticket')
|
||||
->name('reopen');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue