diff --git a/CHANGELOG.md b/CHANGELOG.md index e379462..1479687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/Enums/Credit/AccountType.php b/app/Enums/Credit/AccountType.php new file mode 100644 index 0000000..9373f71 --- /dev/null +++ b/app/Enums/Credit/AccountType.php @@ -0,0 +1,38 @@ + 0, + 'seo' => 1 + ]; + } + + /** + * Enum labels + * + * @return array + */ + protected static function labels(): array + { + return [ + 'development' => trans('Jours de développement'), + 'seo' => trans('Jours de seo') + ]; + } +} diff --git a/app/Enums/Credit/BalanceOperation.php b/app/Enums/Credit/BalanceOperation.php new file mode 100644 index 0000000..5181f8a --- /dev/null +++ b/app/Enums/Credit/BalanceOperation.php @@ -0,0 +1,38 @@ + 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') + ]; + } +} diff --git a/app/Enums/Credit/EstimationStatus.php b/app/Enums/Credit/EstimationStatus.php new file mode 100644 index 0000000..d1769f3 --- /dev/null +++ b/app/Enums/Credit/EstimationStatus.php @@ -0,0 +1,41 @@ + 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'), + ]; + } +} diff --git a/app/Enums/Role.php b/app/Enums/Role.php index 193af5f..f7da12f 100644 --- a/app/Enums/Role.php +++ b/app/Enums/Role.php @@ -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 [ diff --git a/app/Http/Controllers/BrandController.php b/app/Http/Controllers/BrandController.php new file mode 100644 index 0000000..39e36ea --- /dev/null +++ b/app/Http/Controllers/BrandController.php @@ -0,0 +1,51 @@ +__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()); + } +} diff --git a/app/Http/Controllers/Credit/AccountController.php b/app/Http/Controllers/Credit/AccountController.php new file mode 100644 index 0000000..3f537f1 --- /dev/null +++ b/app/Http/Controllers/Credit/AccountController.php @@ -0,0 +1,118 @@ +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(); + } +} diff --git a/app/Http/Controllers/Credit/CreditLogController.php b/app/Http/Controllers/Credit/CreditLogController.php new file mode 100644 index 0000000..d8bedee --- /dev/null +++ b/app/Http/Controllers/Credit/CreditLogController.php @@ -0,0 +1,55 @@ +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()); + } +} diff --git a/app/Http/Controllers/Credit/EstimationController.php b/app/Http/Controllers/Credit/EstimationController.php new file mode 100644 index 0000000..73083cf --- /dev/null +++ b/app/Http/Controllers/Credit/EstimationController.php @@ -0,0 +1,102 @@ +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( + '%s a modifié le statut en %s, 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(); + } +} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 3125a1d..b2b723f 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -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(); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 60ad4c2..70e4bd1 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -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, - ], + ] ]; /** diff --git a/app/Http/Requests/Credit/AddCreditToAccountRequest.php b/app/Http/Requests/Credit/AddCreditToAccountRequest.php new file mode 100644 index 0000000..9b3c410 --- /dev/null +++ b/app/Http/Requests/Credit/AddCreditToAccountRequest.php @@ -0,0 +1,41 @@ +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)"); + } + } + ], + ]; + } +} diff --git a/app/Http/Requests/Credit/AnswerEstimateRequest.php b/app/Http/Requests/Credit/AnswerEstimateRequest.php new file mode 100644 index 0000000..4af0210 --- /dev/null +++ b/app/Http/Requests/Credit/AnswerEstimateRequest.php @@ -0,0 +1,48 @@ +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' + ]; + } +} diff --git a/app/Http/Requests/Credit/CreateEstimateRequest.php b/app/Http/Requests/Credit/CreateEstimateRequest.php new file mode 100644 index 0000000..453c293 --- /dev/null +++ b/app/Http/Requests/Credit/CreateEstimateRequest.php @@ -0,0 +1,67 @@ +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' + ]; + } +} diff --git a/app/Http/Requests/MacroSchedule/PostActivityRequest.php b/app/Http/Requests/MacroSchedule/PostActivityRequest.php index ec3d8de..7962cc1 100644 --- a/app/Http/Requests/MacroSchedule/PostActivityRequest.php +++ b/app/Http/Requests/MacroSchedule/PostActivityRequest.php @@ -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(); } /** diff --git a/app/Models/Brand.php b/app/Models/Brand.php new file mode 100644 index 0000000..8add9ef --- /dev/null +++ b/app/Models/Brand.php @@ -0,0 +1,117 @@ + [ + '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); + } +} diff --git a/app/Models/Credit/Account.php b/app/Models/Credit/Account.php new file mode 100644 index 0000000..e5b58b1 --- /dev/null +++ b/app/Models/Credit/Account.php @@ -0,0 +1,144 @@ + 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); + } +} diff --git a/app/Models/Credit/CreditLog.php b/app/Models/Credit/CreditLog.php new file mode 100644 index 0000000..3f654bd --- /dev/null +++ b/app/Models/Credit/CreditLog.php @@ -0,0 +1,122 @@ + 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'); + } +} diff --git a/app/Models/Credit/Estimation.php b/app/Models/Credit/Estimation.php new file mode 100644 index 0000000..e855e87 --- /dev/null +++ b/app/Models/Credit/Estimation.php @@ -0,0 +1,108 @@ + 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'); + } +} diff --git a/app/Models/Credit/EstimationLog.php b/app/Models/Credit/EstimationLog.php new file mode 100644 index 0000000..d2d6c57 --- /dev/null +++ b/app/Models/Credit/EstimationLog.php @@ -0,0 +1,15 @@ + [ - '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 * diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index 4cdc6f5..d703d0f 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -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; + } } diff --git a/app/Policies/AccountPolicy.php b/app/Policies/AccountPolicy.php new file mode 100644 index 0000000..882c03a --- /dev/null +++ b/app/Policies/AccountPolicy.php @@ -0,0 +1,39 @@ +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) + ] + ]; + } +} diff --git a/app/Policies/BrandPolicy.php b/app/Policies/BrandPolicy.php new file mode 100644 index 0000000..0c6d0e9 --- /dev/null +++ b/app/Policies/BrandPolicy.php @@ -0,0 +1,38 @@ +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) + ] + ]; + } +} diff --git a/app/Policies/CreditLogPolicy.php b/app/Policies/CreditLogPolicy.php new file mode 100644 index 0000000..99c90bf --- /dev/null +++ b/app/Policies/CreditLogPolicy.php @@ -0,0 +1,34 @@ +policies = [ + 'creditlog' => [ + 'viewAny' => URR::make('allowEveryOne') + ] + ]; + } +} diff --git a/app/Policies/EstimationPolicy.php b/app/Policies/EstimationPolicy.php new file mode 100644 index 0000000..05ca7c1 --- /dev/null +++ b/app/Policies/EstimationPolicy.php @@ -0,0 +1,34 @@ +policies = [ + 'estimation' => [ + 'answer' => EstimationStaticRules::make('estimateIsPending', $model) + ] + ]; + } +} diff --git a/app/Policies/LogPolicy.php b/app/Policies/LogPolicy.php index becb137..b2a7c67 100644 --- a/app/Policies/LogPolicy.php +++ b/app/Policies/LogPolicy.php @@ -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) { /** diff --git a/app/Policies/Rules/EstimationStaticRules.php b/app/Policies/Rules/EstimationStaticRules.php new file mode 100644 index 0000000..75069fc --- /dev/null +++ b/app/Policies/Rules/EstimationStaticRules.php @@ -0,0 +1,25 @@ +status->equals(EstimationStatus::pending()); + } +} diff --git a/app/Policies/Rules/LogStaticRules.php b/app/Policies/Rules/LogStaticRules.php index eafeadc..042dbbd 100644 --- a/app/Policies/Rules/LogStaticRules.php +++ b/app/Policies/Rules/LogStaticRules.php @@ -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; } diff --git a/app/Policies/Rules/TicketStaticRules.php b/app/Policies/Rules/TicketStaticRules.php index f9cf42a..d7269b6 100644 --- a/app/Policies/Rules/TicketStaticRules.php +++ b/app/Policies/Rules/TicketStaticRules.php @@ -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; + } } diff --git a/app/Policies/Rules/UserRoleRules.php b/app/Policies/Rules/UserRoleRules.php index 034aca1..1258368 100644 --- a/app/Policies/Rules/UserRoleRules.php +++ b/app/Policies/Rules/UserRoleRules.php @@ -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; } diff --git a/app/Policies/SitePolicy.php b/app/Policies/SitePolicy.php index 8e9523b..ee6a055 100644 --- a/app/Policies/SitePolicy.php +++ b/app/Policies/SitePolicy.php @@ -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) { /** diff --git a/app/Policies/TicketPolicy.php b/app/Policies/TicketPolicy.php index fd2fca7..04659d1 100644 --- a/app/Policies/TicketPolicy.php +++ b/app/Policies/TicketPolicy.php @@ -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) + ]) ] ]; } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index f1f6c37..52a25c4 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -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); diff --git a/composer.json b/composer.json index c039c86..bb0831b 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index 1c50d9c..3062eb0 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/config/auth.php b/config/auth.php index 6f77186..37b0aa1 100644 --- a/config/auth.php +++ b/config/auth.php @@ -39,13 +39,7 @@ return [ 'web' => [ 'driver' => 'session', 'provider' => 'users', - ], - - 'api' => [ - 'driver' => 'token', - 'provider' => 'users', - 'hash' => false, - ], + ] ], /* diff --git a/config/crud-policies.php b/config/crud-policies.php new file mode 100644 index 0000000..b536acf --- /dev/null +++ b/config/crud-policies.php @@ -0,0 +1,50 @@ + '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 +]; diff --git a/database/migrations/2022_05_30_091333_create_brands_table.php b/database/migrations/2022_05_30_091333_create_brands_table.php new file mode 100644 index 0000000..48fa439 --- /dev/null +++ b/database/migrations/2022_05_30_091333_create_brands_table.php @@ -0,0 +1,34 @@ +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'); + } +} diff --git a/database/migrations/2022_05_30_091340_add_brand_id_to_sites.php b/database/migrations/2022_05_30_091340_add_brand_id_to_sites.php new file mode 100644 index 0000000..8dfd1f2 --- /dev/null +++ b/database/migrations/2022_05_30_091340_add_brand_id_to_sites.php @@ -0,0 +1,38 @@ +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'); + }); + } +} diff --git a/database/migrations/2022_05_30_091401_create_credit_accounts_table.php b/database/migrations/2022_05_30_091401_create_credit_accounts_table.php new file mode 100644 index 0000000..d199396 --- /dev/null +++ b/database/migrations/2022_05_30_091401_create_credit_accounts_table.php @@ -0,0 +1,41 @@ +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'); + } +} diff --git a/database/migrations/2022_05_30_091412_create_credit_logs_table.php b/database/migrations/2022_05_30_091412_create_credit_logs_table.php new file mode 100644 index 0000000..b28e4c0 --- /dev/null +++ b/database/migrations/2022_05_30_091412_create_credit_logs_table.php @@ -0,0 +1,39 @@ +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'); + } +} diff --git a/database/migrations/2022_05_30_091438_create_credit_estimations_table.php b/database/migrations/2022_05_30_091438_create_credit_estimations_table.php new file mode 100644 index 0000000..d49a6db --- /dev/null +++ b/database/migrations/2022_05_30_091438_create_credit_estimations_table.php @@ -0,0 +1,53 @@ +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'); + } +} diff --git a/database/migrations/2022_05_30_091453_create_credit_estimation_log_table.php b/database/migrations/2022_05_30_091453_create_credit_estimation_log_table.php new file mode 100644 index 0000000..b9758b5 --- /dev/null +++ b/database/migrations/2022_05_30_091453_create_credit_estimation_log_table.php @@ -0,0 +1,40 @@ +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'); + } +} diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index b4c3ce6..08b8e2a 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -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') diff --git a/resources/lang/en/models.php b/resources/lang/en/models.php index 7f0e46d..a90e8fc 100644 --- a/resources/lang/en/models.php +++ b/resources/lang/en/models.php @@ -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', ] ]; diff --git a/resources/lang/fr/models.php b/resources/lang/fr/models.php index 0acf129..c3c3b0d 100644 --- a/resources/lang/fr/models.php +++ b/resources/lang/fr/models.php @@ -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', ] ]; diff --git a/resources/views/accounts/credit.blade.php b/resources/views/accounts/credit.blade.php new file mode 100644 index 0000000..11d3646 --- /dev/null +++ b/resources/views/accounts/credit.blade.php @@ -0,0 +1,33 @@ +@extends('layout.app') + +@section('bodyClass', 'col-md-8') + +@section('content') +
+
+

+ + {{ $brand->name }} : + {{ $account->label }}  +  {{ __('Créditer un compte') }} +

+
+
+
+ @csrf + @method('PUT') +
+ + + @include('modules.input-error', [ + 'inputName' => 'amount', 'helperId' => 'amountHelper' , 'helper' => __('Le montant en homme jour à créditer') + ]) +
+ +
+ +
+
+
+
+@endsection diff --git a/resources/views/accounts/index.blade.php b/resources/views/accounts/index.blade.php new file mode 100644 index 0000000..36ee7e3 --- /dev/null +++ b/resources/views/accounts/index.blade.php @@ -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') +
+ @include('crud-policies::crud.modules.flashMessage') +
+ @include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()]) + @include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()]) +
+
+
+
+ {{ $models->links() }} +
+
+
+
+ + + + {{ __('crud-policies::crud.reset') }} + +
+
+
+
+ + + @include('crud-policies::crud.modules.sorter') + + + @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) + + @foreach ($modelClass::getEditableProperties() as $fieldName => $prop) + @if(in_array(CrudAction::viewAny(), $prop['actions'])) + @switch($prop['type']) + @case(CrudType::password()) + {{-- Ignore --}} + @break + @default + + @endswitch + @endif + @endforeach + + {{-- MODEL ACTIONS --}} + + + @endforeach + @else + + + + @endif + +
@include('crud-policies::crud.printfield', [ + 'action' => CrudAction::viewAny(), + 'field' => $model->{$fieldName}, + 'props' => $prop, + "$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null + ]) +
+ @can('viewAny', \App\Models\Credit\CreditLog::class) + @if(is_object(request()->brand) and get_class(request()->brand) === App\Models\Brand::class) + {{ __('Historique') }} + @else + {{ __('Historique') }} + @endif + @endcan + @can('credit', $model) + @if(is_object(request()->brand) and get_class(request()->brand) === App\Models\Brand::class) + {{ __('Créditer') }} + @endif + @endcan + @can('view', $model) + {{ __('crud-policies::crud.view') }} + @endcan + @can('update', $model) + {{ __('crud-policies::crud.edit') }} + @endcan + @can('delete', $model) +
+ @csrf + @method('DELETE') + +
+ @endcan +
+
+ {{ __('crud-policies::crud.nothingtoshow') }} +
+ {{ $models->links() }} +
+
+@include('crud-policies::crud.modules.scriptConstants') +@endsection diff --git a/resources/views/brands/index.blade.php b/resources/views/brands/index.blade.php new file mode 100644 index 0000000..9fb5b29 --- /dev/null +++ b/resources/views/brands/index.blade.php @@ -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') +
+ @include('crud-policies::crud.modules.flashMessage') +
+ @include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()]) + @include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()]) +
+
+
+
+ {{ $models->links() }} +
+
+
+
+ + + + {{ __('crud-policies::crud.reset') }} + +
+
+
+
+ + + @include('crud-policies::crud.modules.sorter') + + + @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) + + @foreach ($modelClass::getEditableProperties() as $fieldName => $prop) + @if(in_array(CrudAction::viewAny(), $prop['actions'])) + @switch($prop['type']) + @case(CrudType::password()) + {{-- Ignore --}} + @break + @default + + @endswitch + @endif + @endforeach + + {{-- MODEL ACTIONS --}} + + + @endforeach + @else + + + + @endif + +
@include('crud-policies::crud.printfield', [ + 'action' => CrudAction::viewAny(), + 'field' => $model->{$fieldName}, + 'props' => $prop, + "$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null + ]) +
+ @can('viewAny', \App\Models\Credit\Account::class) + {{ __('models.classes.accounts') }} + @endcan + @can('view', $model) + {{ __('crud-policies::crud.view') }} + @endcan + @can('update', $model) + {{ __('crud-policies::crud.edit') }} + @endcan + @can('delete', $model) +
+ @csrf + @method('DELETE') + +
+ @endcan +
+
+ {{ __('crud-policies::crud.nothingtoshow') }} +
+ {{ $models->links() }} +
+
+@include('crud-policies::crud.modules.scriptConstants') +@endsection diff --git a/resources/views/credit_logs/index.blade.php b/resources/views/credit_logs/index.blade.php new file mode 100644 index 0000000..b9a7385 --- /dev/null +++ b/resources/views/credit_logs/index.blade.php @@ -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') +
+ @include('crud-policies::crud.modules.flashMessage') +
+ @include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()]) + @include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()]) +
+
+
+
+ {{ $models->links() }} +
+
+
+
+ + + + {{ __('crud-policies::crud.reset') }} + +
+
+
+
+ + + @include('crud-policies::crud.modules.sorter') + + + @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) + + @foreach ($modelClass::getEditableProperties() as $fieldName => $prop) + @if(in_array(CrudAction::viewAny(), $prop['actions'])) + @if($fieldName === 'amount') + + @continue + @endif + @if($fieldName === 'type') + + @continue + @endif + @switch($prop['type']) + @case(CrudType::password()) + {{-- Ignore --}} + @break + @default + + @endswitch + @endif + @endforeach + + {{-- MODEL ACTIONS --}} + + + @endforeach + @else + + + + @endif + +
{{ __(':amount jour(s)/homme', ['amount' => number_format($model->{$fieldName}, 1, ',', '')]) }}{{ $model->{$fieldName}->label }}@include('crud-policies::crud.printfield', [ + 'action' => CrudAction::viewAny(), + 'field' => $model->{$fieldName}, + 'props' => $prop, + "$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null + ]) +
+ @can('view', $model) + {{ __('crud-policies::crud.view') }} + @endcan + @can('update', $model) + {{ __('crud-policies::crud.edit') }} + @endcan + @can('delete', $model) +
+ @csrf + @method('DELETE') + +
+ @endcan +
+
+ {{ __('crud-policies::crud.nothingtoshow') }} +
+ {{ $models->links() }} +
+
+@include('crud-policies::crud.modules.scriptConstants') +@endsection diff --git a/resources/views/estimations/create.blade.php b/resources/views/estimations/create.blade.php new file mode 100644 index 0000000..912a009 --- /dev/null +++ b/resources/views/estimations/create.blade.php @@ -0,0 +1,25 @@ +@extends('layout.app') + +@section('bodyClass', 'col-md-8') + +@section('content') +
+
+

+ + {{ $ticket->full_name }} + @if ($ticket->is_internal)@endif +  {{ __('Créer une estimation') }} +

+
+
+
+ @csrf + @include('estimations.form-inputs') +
+ +
+
+
+
+@endsection diff --git a/resources/views/estimations/form-inputs.blade.php b/resources/views/estimations/form-inputs.blade.php new file mode 100644 index 0000000..3b9e639 --- /dev/null +++ b/resources/views/estimations/form-inputs.blade.php @@ -0,0 +1,19 @@ +
+ + + @include('modules.input-error', [ + 'inputName' => 'amount', 'helperId' => 'amountHelper' , 'helper' => __('Le montant en homme jour estimé.') + ]) +
+
+ + + @include('modules.input-error', [ + 'inputName' => 'brand_id', 'helperId' => 'creditAccountHelp' , 'helper' => __('Le compte de la marque à débiter') + ]) +
diff --git a/resources/views/estimations/pendingCard.blade.php b/resources/views/estimations/pendingCard.blade.php new file mode 100644 index 0000000..b20586d --- /dev/null +++ b/resources/views/estimations/pendingCard.blade.php @@ -0,0 +1,45 @@ +
+
+ {{ __('Estimation faite par :user', ['user' => $estimation->estimator->name]) }} +
+
+
+
+
+ {{ __('Temps estimé :time jours/homme', ['time' => $estimation->amount]) }} +
+ @if($estimation->status->equals(App\Enums\Credit\EstimationStatus::pending())) +
+ {{ __('En attente') }} +
+ @endif + @if($estimation->status->equals(App\Enums\Credit\EstimationStatus::accepted())) +
+ {{ __('Validé par :user', ['user' => optional($estimation->validator)->name ?? 'personne supprimée']) }} +
+ @endif + @if($estimation->status->equals(App\Enums\Credit\EstimationStatus::refused())) +
+ {{ __('Refusé par :user', ['user' => optional($estimation->validator)->name ?? 'personne supprimée']) }} +
+ @endif + @can('validateEstimate', $ticket) + @can('answer', $estimation) +
+
+ @csrf + + +
+
+ @csrf + + +
+
+ @endcan + @endcan +
+
+
+
diff --git a/resources/views/layout/nav.blade.php b/resources/views/layout/nav.blade.php index 54e4da3..121b43e 100644 --- a/resources/views/layout/nav.blade.php +++ b/resources/views/layout/nav.blade.php @@ -13,24 +13,44 @@