Merge branch 'trunk'

This commit is contained in:
Vincent BRIDIER 2023-08-18 12:37:30 +02:00
commit 7559a47044
No known key found for this signature in database
GPG key ID: 8571BFA20420D512
47 changed files with 144 additions and 2130 deletions

View file

@ -1,4 +1,3 @@
# TODO
- App\Http\Controllers\Bo\UserController::stats() : refator this using carbon.
- App\Models\Message:booted(): investigate why this make errors on prod,

View file

@ -0,0 +1,36 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class TestMail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'uwil:mail-test';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send test email to equipe.web@ldc.fr';
/**
* Execute the console command.
*
* @return integer
*/
public function handle()
{
Mail::raw(sprintf('UWIL mail-test from %s %s', \config('app.name'), \config('app.url')), function ($msg) {
$msg->to('equipe.web@ldc.fr')->subject('Test Email');
});
return 0;
}
}

View file

@ -1,118 +0,0 @@
<?php
namespace App\Enums\Planning;
use App\Traits\Enums\BaseEnum;
use JsonSerializable;
enum ActivityTypeEnum: string implements JsonSerializable
{
use BaseEnum;
case support = 'support';
case rnd = 'rnd';
case dev = 'dev';
case projects = 'projects';
case management = 'management';
case absence = 'absence';
case leaddev = 'leaddev';
/**
* Optionnal labels definition
*/
private const LABELS = [
self::support->name => 'Support',
self::rnd->name => 'R&D',
self::dev->name => 'Développement/Facturable',
self::projects->name => 'Projets & Réunions',
self::management->name => 'Organisation management',
self::absence->name => 'Absence',
self::leaddev->name => 'Lead Dev',
];
/**
* Optionnal labels definition
*/
private const COLORS = [
self::support->name => '#c6e0b4',
self::rnd->name => '#b4c6e7',
self::dev->name => '#00b0f0',
self::projects->name => '#ffe699',
self::management->name => '#f4b084',
self::absence->name => '#aeaaaa',
self::leaddev->name => '#ff7979',
];
/**
* Get Color
*
* @return string
*/
public function color(): string
{
return self::COLORS[$this->name];
}
/**
* Get enum colors
*
* @return array<string>
*/
public static function toColors(): array
{
return self::COLORS;
}
/**
* Datas to send with json_encode.
*
* @return mixed
*/
public function jsonSerialize(): mixed
{
return match ($this) {
self::support => [
'label' => self::support->label(),
'name' => self::support->name(),
'value' => self::support->value(),
'color' => self::support->color()
],
self::rnd => [
'label' => self::rnd->label(),
'name' => self::rnd->name(),
'value' => self::rnd->value(),
'color' => self::rnd->color()
],
self::dev => [
'label' => self::dev->label(),
'name' => self::dev->name(),
'value' => self::dev->value(),
'color' => self::dev->color()
],
self::projects => [
'label' => self::projects->label(),
'name' => self::projects->name(),
'value' => self::projects->value(),
'color' => self::projects->color()
],
self::management => [
'label' => self::management->label(),
'name' => self::management->name(),
'value' => self::management->value(),
'color' => self::management->color()
],
self::absence => [
'label' => self::absence->label(),
'name' => self::absence->name(),
'value' => self::absence->value(),
'color' => self::absence->color()
],
self::leaddev => [
'label' => self::leaddev->label(),
'name' => self::leaddev->name(),
'value' => self::leaddev->value(),
'color' => self::leaddev->color()
],
}; //end match
}
}

View file

@ -1,82 +0,0 @@
<?php
namespace App\Enums\Planning;
use App\Traits\Enums\BaseEnum;
use JsonSerializable;
enum CellModelEnum: string implements JsonSerializable
{
use BaseEnum;
case ticket = 'tickets';
case site = 'sites';
case project = 'projects';
/**
* Optionnal labels definition
*/
private const LABELS = [
self::ticket->name => 'Ticket',
self::site->name => 'Site',
self::project->name => 'Projet',
];
/**
* Optionnal tables definition
*
* This is now a const, so we can't do (new Ticket())->getTable() anymore,
* we need to write it as a constant string.
*/
private const TABLES = [
self::ticket->name => 'tickets',
self::site->name => 'sites',
self::project->name => 'projects',
];
/**
* Get Table
*
* @return string
*/
public function table(): string
{
return self::TABLES[$this->name];
}
/**
* Get enum tables
*
* @return array<string>
*/
public static function toTables(): array
{
return self::TABLES;
}
/**
* Datas to send with json_encode.
*
* @return mixed
*/
public function jsonSerialize(): mixed
{
return match ($this) {
self::ticket => [
'label' => self::ticket->label(),
'name' => self::ticket->name(),
'value' => self::ticket->value()
],
self::site => [
'label' => self::site->label(),
'name' => self::site->name(),
'value' => self::site->value()
],
self::project => [
'label' => self::project->label(),
'name' => self::project->name(),
'value' => self::project->value()
],
}; //end match
}
}

View file

@ -1,77 +0,0 @@
<?php
namespace App\Enums\Planning;
use App\Traits\Enums\BaseEnum;
enum CellTypeEnum: string
{
use BaseEnum;
case support = 'support';
case rnd = 'rnd';
case dev = 'dev';
case projects = 'projects';
case management = 'management';
case absence = 'absence';
case leaddev = 'leaddev';
/**
* Optionnal values definition
*/
private const VALUES = [
self::support->name => '#c6e0b4',
self::rnd->name => '#b4c6e7',
self::dev->name => '#00b0f0',
self::projects->name => '#ffe699',
self::management->name => '#f4b084',
self::absence->name => '#aeaaaa',
self::leaddev->name => '#ff7979',
];
/**
* Optionnal labels definition
*/
private const LABELS = [
self::support->name => 'Support',
self::rnd->name => 'R&D',
self::dev->name => 'Développement/Facturable',
self::projects->name => 'Projets & Réunions',
self::management->name => 'Organisation management',
self::absence->name => 'Absence',
self::leaddev->name => 'Lead Dev',
];
/**
* Optionnal colors definition
*/
private const COLORS = [
self::support->name => '#c6e0b4',
self::rnd->name => '#b4c6e7',
self::dev->name => '#00b0f0',
self::projects->name => '#ffe699',
self::management->name => '#f4b084',
self::absence->name => '#aeaaaa',
self::leaddev->name => '#ff7979',
];
/**
* Get Color
*
* @return string
*/
public function color(): string
{
return self::COLORS[$this->name];
}
/**
* Get enum colors
*
* @return array<string>
*/
public static function toColors(): array
{
return self::COLORS;
}
}

View file

@ -1,33 +0,0 @@
<?php
namespace App\Enums\Planning;
use App\Traits\Enums\BaseEnum;
enum TableRowEnum: int
{
use BaseEnum;
case matin1 = 1;
case matin2 = 2;
case matin3 = 3;
case matin4 = 4;
case apresmidi1 = 5;
case apresmidi2 = 6;
case apresmidi3 = 7;
case apresmidi4 = 8;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::matin1->name => "Matin 1",
self::matin2->name => "Matin 2",
self::matin3->name => "Matin 3",
self::matin4->name => "Matin 4",
self::apresmidi1->name => "Après-midi 1",
self::apresmidi2->name => "Après-midi 2",
self::apresmidi3->name => "Après-midi 3",
self::apresmidi4->name => "Après-midi 4",
];
}

View file

@ -1,31 +0,0 @@
<?php
namespace App\Enums\Projects;
use App\Traits\Enums\BaseEnum;
enum ProjectStatusEnum: int
{
use BaseEnum;
case new = 0;
case specification = 1;
case validated = 2;
case development = 3;
case recipe = 4;
case production = 5;
case finished = 6;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::new->name => "Nouveau",
self::specification->name => "Spécifications",
self::validated->name => "Validé",
self::development->name => "En développement",
self::recipe->name => "En recettage",
self::production->name => "En production",
self::finished->name => "Terminé",
];
}

View file

@ -156,14 +156,14 @@ class InvoiceController extends Controller
->route('bo.invoices.edit', $invoice)
->with('success', __(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.invoices'))]
['model' => __('models.classes.invoices')]
));
} //end if
return redirect()
->back()
->with('error', __(
'crud.messages.cannot_be_updated',
['model' => Str::singular(__('models.classes.invoices'))]
['model' => __('models.classes.invoices')]
));
});
}

View file

@ -1,256 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Users\RoleEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\StoreScheduleRequest;
use App\Models\Schedule;
use App\Models\Ticket;
use App\Models\User;
use Illuminate\Http\Request;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
class ScheduleController extends Controller
{
/**
* Create the date
*
* @param \Illuminate\Http\Request $request
* @return \Carbon\CarbonImmutable|false
*/
private function dateCreation(Request $request)
{
$date = CarbonImmutable::now()->startOfWeek();
if ($request->p) {
$date = CarbonImmutable::createFromFormat('d/m/Y', $request->p)->startOfWeek();
}
return $date;
}
/**
* Create the users table.
*
* @param \Illuminate\Http\Request $request
* @return array
* @throws \Illuminate\Contracts\Container\BindingResolutionException BindingResolutionException.
*/
private function createUsers(Request $request)
{
$users = [];
if (!is_null($request->cookie('userAuthSchedule'))) {
$dbusers = User::where('id', auth()->user()->id)->get();
} else {
$dbusers = User::where('role', RoleEnum::developper)->get();
}
foreach ($dbusers as $user) {
$users[$user->id] = ['name' => $user->name];
}
return $users;
}
/**
* Display schedule.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse
* @throws \Illuminate\Contracts\Container\BindingResolutionException BindingResolutionException.
* @throws \Symfony\Component\Routing\Exception\RouteNotFoundException RouteNotFoundException.
*/
public function index(Request $request)
{
if (!\auth()->user()->role->equals(RoleEnum::developper) && !is_null($request->cookie('userAuthSchedule'))) {
return $this->showAllSchedules();
}
$date = $this->dateCreation($request);
$nbweeks = config('schedules.shownWeeks');
$users = $this->createUsers($request);
$schedules = Schedule::whereHas('user', function (Builder $query) use ($request) {
$query->where('deleted_at', null);
$query->where('role', RoleEnum::developper);
if (!is_null($request->cookie('userAuthSchedule'))) {
$query->where('id', auth()->user()->id);
}
})
->whereDate('date', '>=', $date)
->whereDate('date', '<=', $date->addDays($nbweeks * 7 - 1))
->orderBy('user_id')
->orderBy('slot')
->orderBy('date')
->get();
$arSchedules = array();
$prevUserId = null;
$prevDate = null;
$prevSlot = null;
foreach ($schedules as $schedule) {
$id_lie = ['label' => null, 'name' => null, 'value' => ''];
if (!is_null($schedule->model_lie)) {
if (isset($schedule->relatedModel->name)) {
$id_lie = [
'label' => $schedule->relatedModel->name,
'name' => $schedule->relatedModel->name,
'value' => $schedule->id_lie
];
} else {
$ticket = Ticket::find($schedule->id_lie);
$id_lie = [
'label' => 'Ticket ' . $schedule->id_lie,
'name' => $ticket->formatNameForSchedule($ticket),
'value' => $schedule->id_lie
];
}
$model_lie = $schedule->model_lie;
} else {
$model_lie = ['label' => null, 'value' => null];
}//end if
if ($prevUserId != $schedule->user_id) {
// New user.
$arSchedules[$schedule->user_id] = array(
$schedule->slot => array(
$schedule->date->format('d/m/Y') => array(
'id' => $schedule->id,
'type' => $schedule->activity,
'text' => $schedule->description,
'id_lie' => $id_lie,
'model_lie' => $model_lie,
),
)
);
} else {
// Same user.
if ($prevSlot != $schedule->slot) {
// New slot.
$arSchedules[$schedule->user_id][$schedule->slot] = array(
$schedule->date->format('d/m/Y') => array(
'id' => $schedule->id,
'type' => $schedule->activity,
'text' => $schedule->description,
'id_lie' => $id_lie,
'model_lie' => $model_lie,
),
);
} else {
// Same slot.
if ($prevDate != $schedule->date) {
// New date.
$arSchedules[$schedule->user_id][$schedule->slot][$schedule->date->format('d/m/Y')] = array(
'id' => $schedule->id,
'type' => $schedule->activity,
'text' => $schedule->description,
'id_lie' => $id_lie,
'model_lie' => $model_lie,
);
}
} //end if
} //end if
$prevUserId = $schedule->user_id;
$prevDate = $schedule->date;
$prevSlot = $schedule->slot;
} //end foreach
$weekCells = array();
for ($i = 0; $i < $nbweeks; $i++) {
$weekCells[] = array(
"num" => $date->addDays($i * 7)->weekOfYear,
"month" => $date->addDays($i * 7)->shortLocaleMonth,
"year" => $date->addDays($i * 7)->format('Y')
);
}
return view('bo.pages.schedule.index', compact('users', 'date', 'weekCells', 'arSchedules'));
}
/**
* Display cell content.
*
* @param \App\Http\Requests\Bo\StoreScheduleRequest $request
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Contracts\Container\BindingResolutionException BindingResolutionException.
*/
public function setCellContent(StoreScheduleRequest $request)
{
$date = CarbonImmutable::createFromFormat('d/m/Y', $request->date);
$fill = $request->all();
$fill['date'] = $date;
$schedule = Schedule::updateOrCreate(['id' => $request->id], $fill);
$schedule->saveOrFail();
$id_lie = array('label' => null, 'name' => null, 'value' => null);
if (!is_null($schedule->model_lie)) {
if (isset($schedule->relatedModel->name)) {
$id_lie = array(
'label' => $schedule->relatedModel->name,
'name' => $schedule->relatedModel->name,
'value' => $schedule->id_lie
);
} else {
$id_lie = array(
'label' => 'Ticket ' . $schedule->id_lie,
'name' => Ticket::find($schedule->id_lie)->formatNameForSchedule(Ticket::find($schedule->id_lie)),
'value' => $schedule->id_lie
);
}
$model_lie = $schedule->model_lie;
} else {
$model_lie = array('label' => null, 'name' => null, 'value' => null);
}
$arSchd = [
'id' => $schedule->id,
'user_id' => $schedule->user_id,
'date' => $schedule->date->format('d/m/Y'),
'activity' => $schedule->activity,
'description' => $schedule->description,
'id_lie' => $id_lie,
'model_lie' => $model_lie,
'slot' => $schedule->slot,
];
return \response()->json(['status' => true, 'sched' => $arSchd]);
}
/**
* Delet cell content.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Contracts\Container\BindingResolutionException BindingResolutionException.
*/
public function eraseCellContent(Request $request)
{
$schedule = Schedule::findOrFail($request->id);
$schedule->delete();
return \response()->json(['status' => true]);
}
/**
* Display the auth user schedule.
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Contracts\Container\BindingResolutionException BindingResolutionException.
* @throws \Symfony\Component\Routing\Exception\RouteNotFoundException RouteNotFoundException.
*/
public function showMySchedule()
{
return \redirect()->route('bo.schedule.index')->withCookie(cookie()->forever('userAuthSchedule', 'true'));
}
/**
* Display all the dev schedule.
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Contracts\Container\BindingResolutionException BindingResolutionException.
* @throws \Symfony\Component\Routing\Exception\RouteNotFoundException RouteNotFoundException.
*/
public function showAllSchedules()
{
return \redirect()->route('bo.schedule.index')->withCookie(cookie()->forget('userAuthSchedule'));
}
}

View file

@ -117,7 +117,7 @@ class TicketController extends Controller
{
if ($request->ajax()) {
return \response()->json([
'label' => $ticket->formatNameForSchedule($ticket),
'label' => $ticket->formatName($ticket),
'value' => $ticket->id
]);
}
@ -622,7 +622,7 @@ class TicketController extends Controller
return ToolboxHelper::jsonPaginate(
$this->customPaginate($tickets, $request->pagination),
'formatNameForSchedule'
'formatName'
);
});
}

View file

@ -188,34 +188,6 @@ class UserController extends Controller
return app(ForgotPasswordController::class)->sendResetLinkEmail($request);
}
/**
* Show stats
*
* @return \Illuminate\Contracts\View\View
*/
public function stats(): \Illuminate\Contracts\View\View
{
/** @var \App\Models\User */
$user = auth()->user();
$stats = [];
$curYear = date('Y');
for ($i = 2021; $i <= $curYear; $i++) {
$start = $i . '-03-01';
$end = $i + 1 . '-02-' . $this->getLastDayFebruary($i + 1);
$datas = $user->schedules()->selectRaw('count(id) as nb, activity')
->whereBetween('date', [$start, $end])
->groupBy('activity')
->orderBy('activity', 'ASC')
->get();
$stats[sprintf("%d/%d", $i, $i + 1)] = $datas;
}
return view('bo.pages.users.stats', ['stats' => array_reverse($stats)]);
}
/**
* Get february last day date of the year
*

View file

@ -25,7 +25,7 @@ class StoreBrandRequest extends FormRequest
public function rules()
{
return [
'name' => 'required|string|max:255',
'name' => 'required|string|min:3|max:255',
'sites' => 'nullable|array',
'sites.*.id' => 'required|exists:sites,id'
];

View file

@ -27,8 +27,8 @@ class StoreInformationRequest extends FormRequest
public function rules()
{
return [
'title' => 'required|string|max:255',
'message' => 'required|string|max:255',
'title' => 'required|string|min:3|max:255',
'message' => 'required|string|min:3|max:255',
'type' => [
'required',
Rule::in(collect(InfoColorsEnum::cases())->pluck('value')->all()),

View file

@ -25,8 +25,8 @@ class StorePackageRequest extends FormRequest
public function rules()
{
return [
'name' => 'required|string|max:255',
'amount' => 'required|numeric|regex:/^\d+(\.\d{1,2})?$/',
'name' => 'required|string|min:3|max:255',
'amount' => 'required|numeric|min:0|regex:/^\d+(\.\d{1,2})?$/',
];
}

View file

@ -2,10 +2,8 @@
namespace App\Http\Requests\Bo\Projects;
use App\Enums\Projects\ProjectStatusEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
class StoreProjectRequest extends FormRequest
{
@ -28,10 +26,6 @@ class StoreProjectRequest extends FormRequest
{
return [
'name' => 'required|string|min:3|max:255|unique:projects,name',
'status' => [
'required',
Rule::in(collect(ProjectStatusEnum::cases())->pluck('value')->all()),
],
'site_id' => 'required|exists:sites,id',
];
}

View file

@ -1,80 +0,0 @@
<?php
namespace App\Http\Requests\Bo;
use App\Enums\Planning\ActivityTypeEnum;
use App\Enums\Planning\CellModelEnum;
use App\Enums\Planning\TableRowEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
class StoreScheduleRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth()->check() and $this->ajax();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'user_id' => 'required|exists:users,id',
'date' => 'date_format:d/m/Y',
'activity' => [
'required',
new Enum(ActivityTypeEnum::class),
function ($attribute, $value, $fail) {
if (
\in_array($value, [
ActivityTypeEnum::dev->name(),
ActivityTypeEnum::projects->name(),
]) and
is_null($this->id_lie)
) {
$fail(trans("$attribute : Il est obligatoire d'assigner un type d'élement lié au ticket"));
}
}
],
'slot' => [
'required',
new Enum(TableRowEnum::class)
],
'model_lie' => [
'nullable',
new Enum(CellModelEnum::class),
],
'id_lie' => [
'required_with:model_lie',
'nullable',
'numeric',
],
'description' => 'required|string|min:3|max:90'
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'user_id' => \trans('utilisateur'),
'activity' => \trans('type d\'activitée'),
'model_lie' => \trans('type d\'élément lié'),
'id_lie' => \trans('élément lié'),
'slot' => \trans('ligne de la journée'),
];
}
}

View file

@ -2,10 +2,8 @@
namespace App\Http\Requests\Bo\TicketTypes;
use App\Enums\Tickets\TicketTypeEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
class UpdateTicketTypeRequest extends FormRequest
{
@ -27,7 +25,6 @@ class UpdateTicketTypeRequest extends FormRequest
public function rules(): array
{
return [
'type' => ['required', new Enum(TicketTypeEnum::class)],
'template' => 'required|string|min:3|max:255',
];
}

View file

@ -2,13 +2,11 @@
namespace App\Models;
use App\Enums\Projects\ProjectStatusEnum;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property string $name
* @property ProjectStatusEnum $status
* @property Site $site_id
*/
class Project extends Model
@ -22,19 +20,9 @@ class Project extends Model
*/
protected $fillable = [
'name',
'status',
'site_id'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'status' => ProjectStatusEnum::class
];
// * RELATIONSHIPS.
/**

View file

@ -1,80 +0,0 @@
<?php
namespace App\Models;
use App\Enums\Planning\ActivityTypeEnum;
use App\Enums\Planning\CellModelEnum;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property User $user_id
* @property date $date
* @property integer $slot
* @property ActivityTypeEnum $activity
* @property string $description
* @property Ticket|Site|Projet $id_lie
* @property CellModelEnum $model_lie
*/
class Schedule extends Model
{
use HasFactory;
/**
* The attributes that are fillable.
*
* @var array
*/
protected $fillable = [
'user_id',
'date',
'slot',
'activity',
'description',
'id_lie',
'model_lie'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'date' => 'date',
'activity' => ActivityTypeEnum::class,
'model_lie' => CellModelEnum::class
];
// * RELATIONSHIPS
/**
* BelongsTo User relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Relationship to linked objet
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function relatedModel()
{
switch ($this->model_lie->value) {
case 'sites':
return $this->belongsTo(Site::class, 'id_lie');
case 'projects':
return $this->belongsTo(Project::class, 'id_lie');
case 'tickets':
return $this->belongsTo(Ticket::class, 'id_lie');
default:
return new BelongsTo($this->newQuery(), $this, '', '', '');
}
}
}

View file

@ -265,7 +265,7 @@ class Ticket extends Model
* @param self $ticket
* @return string
*/
public static function formatNameForSchedule(self $ticket): string
public static function formatName(self $ticket): string
{
return sprintf(
'Ticket %d (%s): %s',

View file

@ -191,14 +191,4 @@ class User extends Authenticatable
{
return $this->belongsToMany(Site::class);
}
/**
* HasMany Schedule relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function schedules()
{
return $this->hasMany(Schedule::class);
}
}

View file

@ -1,7 +1,6 @@
<?php
use App\Enums\Invoices\InvoiceStatusEnum;
use App\Models\Invoice;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn('status');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('projects', function (Blueprint $table) {
$table->unsignedSmallInteger('status')->default(0)->comment('Enum ProjectStatus')->after('name');
});
}
};

View file

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class DropSchedulesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::dropIfExists('schedules');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::create('schedules', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->unique();
$table->foreign('user_id')->on('sites')->references('id');
$table->date('date')->unique();
$table->tinyInteger('slot')->unique();
$table->string('activity')->nullable();
$table->text('description');
$table->bigInteger('id_lie');
$table->string('model_lie');
$table->timestamps();
});
}
}

View file

@ -14,7 +14,6 @@ return [
'download' => 'Télécharger',
'historic' => 'Historique',
'credit' => 'Créditer',
'stats' => 'Statistiques',
],
'helpers' => [

View file

@ -13,7 +13,6 @@
:placeholder="intPlaceholder"
:required="intRequired"
@input="onInput"
@blur="onBlur"
v-model="intValue"
/>
</div>
@ -80,18 +79,6 @@ export default defineComponent({
};
},
methods: {
onBlur() {
const el = textarea.value;
if (!el) {
throw new Error("undefined textarea element");
}
// * Remove every new line char
el.value = el.value.replace(/[\n\r]/g, "");
this.$nextTick(() => {
el.style.height = "auto";
el.style.height = `${el.scrollHeight}px`;
});
},
onInput() {
const el = textarea.value;
if (!el) {

File diff suppressed because it is too large Load diff

View file

@ -49,7 +49,7 @@
'helper' => __('Séléctionnez les sites à associer à cet utilisateur (minimum 0, maximum :max)', [
'max' => count($siteModels)
]),
'required' => true,
'required' => false,
])
</div>
</div>

View file

@ -5,7 +5,7 @@
<legend>{{ Str::ucfirst(__('validation.attributes.date')) }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.timestamp-datetime', [
@include('bo.partials.inputs.date', [
'id' => 'dutyhourDate',
'label' => __(':field de la :model', [
'field' => Str::ucfirst(__('validation.attributes.date')),

View file

@ -1,7 +1,7 @@
@extends('layout.app', ['brParam' => $invoiceModel])
@section('title', __('Création d\'un :model', ['model' => Str::singular(__('models.classes.invoices'))]))
@section('description', __('Création d\'un tout nouveau :model', ['model' => Str::singular(__('models.classes.invoices'))]))
@section('title', __('Création d\'un :model', ['model' => __('models.classes.invoices')]))
@section('description', __('Création d\'un tout nouveau :model', ['model' => __('models.classes.invoices')]))
@section('breadcrumb', request()->route()->getName())
@section('content')
@ -22,7 +22,7 @@
class="btn btn-primary"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.save_model', ['model' => Str::singular(__('models.classes.invoices'))]) }}">
title="{{ __('crud.helpers.save_model', ['model' => __('models.classes.invoices')]) }}">
{{ __('crud.actions.save') }}
</button>
</div>
@ -37,7 +37,7 @@
class="btn btn-primary"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.save_model', ['model' => Str::singular(__('models.classes.invoices'))]) }}">
title="{{ __('crud.helpers.save_model', ['model' => __('models.classes.invoices')]) }}">
{{ __('crud.actions.save') }}
</button>
</div>

View file

@ -340,7 +340,7 @@
</div>
</div>
@if($invoiceModel->status === \App\Enums\Invoices\InvoiceStatusEnum::opened)
@if($invoiceModel->status === \App\Enums\Invoices\InvoiceStatusEnum::opened || $invoiceModel->status === null)
@push('scripts')
<script>
document.addEventListener("DOMContentLoaded", () => {

View file

@ -24,7 +24,7 @@
class="btn btn-primary float-right"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.create_model', ['model' => Str::singular(__('models.classes.invoices'))]) }}">
title="{{ __('crud.helpers.create_model', ['model' => __('models.classes.invoices')]) }}">
<i class="fa-solid fa-plus"></i>
</a>
</div>
@ -85,7 +85,7 @@
</tbody>
@else
<tr>
<td class="text-center fw-bold border-0">{{ __('crud.other.no_model_found', ['model' => Str::singular(__('models.classes.invoices'))]) }}</td>
<td class="text-center fw-bold border-0">{{ __('crud.other.no_model_found', ['model' => __('models.classes.invoices')]) }}</td>
</tr>
@endif
</table>

View file

@ -1,7 +1,7 @@
@extends('layout.app', ['brParam' => $invoiceModel])
@section('title', __('Aperçu d\'un :model', ['model' => Str::singular(__('models.classes.invoices'))]))
@section('description', __('Aperçu d\'un :model déjà existant', ['model' => Str::singular(__('models.classes.invoices'))]))
@section('title', __('Aperçu d\'un :model', ['model' => __('models.classes.invoices')]))
@section('description', __('Aperçu d\'un :model déjà existant', ['model' => __('models.classes.invoices')]))
@section('breadcrumb', request()->route()->getName())
@section('content')

View file

@ -48,13 +48,12 @@
'name' => 'amount',
'required' => true,
'model' => $packageModel ?? null,
'helper' => __('validation.between.numeric', [
'helper' => __('validation.min.numeric', [
'attribute' => __(':field du :model', [
'field' => Str::ucfirst(__('validation.attributes.amount.day')),
'model' => Str::singular(__('models.classes.packages')),
]),
'min' => 0,
'max' => 10.000,
]),
'step' => 0.01
])

View file

@ -32,25 +32,6 @@
</div>
</fieldset>
</div>
{{-- STATUS --}}
<div class="col-12 col-md-6 pb-3">
<fieldset class="border h-100 bg-white p-3 mb-3">
<legend>{{ Str::ucfirst(__('validation.attributes.status')) }}</legend>
<div class="row">
<div class="col-12 mb-3">
@include('bo.partials.inputs.tinyint-enum', [
'id' => 'projectStatus',
'label' => __('crud.minicrud.associated', ['entity' => 'Role']),
'name' => 'status',
'model' => $projectModel ?? null,
'required' => true,
'associatedModels' => \App\Enums\Projects\ProjectStatusEnum::toArray(),
'helper' => __('crud.minicrud.select', ['entity' => 'statut']),
])
</div>
</div>
</fieldset>
</div>
{{-- SITE_ID --}}
<div class="col-12 col-md-6 pb-3">
<fieldset class="border bg-white h-100 p-3">

View file

@ -21,7 +21,6 @@
@php
$cols = [
'name' => Str::ucfirst(__('validation.attributes.name')),
'status' => Str::ucfirst(__('validation.attributes.status')),
'site_id' => Str::ucfirst(__('models.classes.sites')),
];
@endphp
@ -34,7 +33,6 @@
@foreach ($projectModels as $projectModel)
<tr class="list-item border-bottom">
<td class="text-center align-middle">{{ $projectModel->name }}</td>
<td class="text-center align-middle">{{ $projectModel->status->label() }}</td>
<td class="text-center align-middle">{{ $projectModel->site->name }}</td>
<td class="text-end align-middle">
<form action="{{ route('bo.projects.destroy', $projectModel->id) }}"

View file

@ -23,10 +23,6 @@
<td class="text-center align-middle">{{ Str::ucfirst(__('validation.attributes.name')) }}</td>
<td class="text-center align-middle">{{ $projectModel->name }}</td>
</tr>
<tr class="list-item border-bottom">
<td class="text-center align-middle">{{ Str::ucfirst(__('validation.attributes.status')) }}</td>
<td class="text-center align-middle">{{ $projectModel->status->label() }}</td>
</tr>
<tr class="list-item border-bottom">
<td class="text-center align-middle">{{ Str::ucfirst(__('validation.attributes.sites')) }}</td>
<td class="text-center align-middle">{{ $projectModel->site->name }}</td>

View file

@ -1,76 +0,0 @@
@extends('layout.app')
@section('title', __('Planning'))
@section('description', __('Planning de l\'équipe Uwil'))
@section('content')
<div id="planning" class="container container-xxl vueComponent">
<div class="row sticky-top bg-white z-2 py-2 mb-3">
<div class="col">
<h4 class="d-inline me-3">{{__('Légende')}}</h4>
<div class="d-inline">
@foreach(\App\Enums\Planning\CellTypeEnum::cases() as $type)
<span class="badge text-dark celltype-{{ Str::slug($type->label()) }} my-1">{{ $type->label() }}</span>
@endforeach
</div>
</div>
@if (\auth()->user()->role->equals(\App\Enums\Users\RoleEnum::developper))
<div class="col-2 text-end p-0">
@if (request()->cookie('userAuthSchedule'))
<a href="{{ route('bo.schedule.showAllSchedules') }}" class="btn btn-primary btn-sm">{{__('Afficher les plannings')}}</a>
@else
<a href="{{ route('bo.schedule.showMySchedule') }}" class="btn btn-primary btn-sm">{{__('Afficher mon planning')}}</a>
@endif
</div>
@endif
</div>
@php
$dataVue = [
'browseDate' => $date,
'schedules' => $arSchedules,
'activities' => \App\Enums\Planning\ActivityTypeEnum::toArray(),
'models' => \App\Enums\Planning\CellModelEnum::toArray(),
'users' => $users,
'weeks' => $weekCells,
'daySlots' => Config::get('schedules.daySlots'),
'showWeekends' => 'false',
];
@endphp
<div id="schedule" data-json='@json($dataVue)'></div>
</div>
@endsection
@push('styles')
<style nonce="{{ $nonce }}">
.support {
background-color: {{ \App\Enums\Planning\CellTypeEnum::support->color() }} !important;
}
.rnd {
background-color: {{ \App\Enums\Planning\CellTypeEnum::rnd->color() }} !important;
}
.dev {
background-color: {{ \App\Enums\Planning\CellTypeEnum::dev->color() }} !important;
}
.projects {
background-color: {{ \App\Enums\Planning\CellTypeEnum::projects->color() }} !important;
}
.management {
background-color: {{ \App\Enums\Planning\CellTypeEnum::management->color() }} !important;
}
.absence, .Sat, .Sun {
background-color: {{ \App\Enums\Planning\CellTypeEnum::absence->color() }} !important;
}
.leaddev {
background-color: {{ \App\Enums\Planning\CellTypeEnum::leaddev->color() }} !important;
}
.empty {
background-color: #FFF !important;
}
@foreach(\App\Enums\Planning\CellTypeEnum::cases() as $type)
.celltype-{{ Str::slug($type->label()) }} {
background-color: {{ $type->color() }};
font-size: 1rem;
}
@endforeach
</style>
@endpush

View file

@ -53,7 +53,7 @@
'min' => 0,
'max' => count($userModels),
]),
'required' => true,
'required' => false,
])
</div>
</div>

View file

@ -1,5 +1,5 @@
<div class="row">
{{-- ROLE --}}
{{-- TYPE --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="border h-100 bg-white p-3">
<legend>{{ trans_choice('models.classes.ticket_types', 1) }}</legend>
@ -12,7 +12,8 @@
]),
'name' => 'type',
'model' => $ticketTypeModel ?? null,
'required' => true,
'required' => false,
'disabled' => $ticketTypeModel ? true : false,
'associatedModels' => \App\Enums\Tickets\TicketTypeEnum::toArray(),
'helper' => __('crud.minicrud.enum_select', [
'enum' => trans_choice('models.classes.ticket_types', 1),

View file

@ -1,58 +0,0 @@
@extends('layout.app')
@section('title', __('Statistiques'))
@section('description', __('Statistiques de l\':model :user', ['model' => Str::singular(__('models.classes.users')), 'user' => auth()->user()->name]))
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center">
@include('breadcrumbs.breadcrumb-body')
</div>
@foreach ($stats as $index => $stat)
<table class="table mt-3">
<thead class="table-dark">
<tr>
<th>{{ $index }}</th>
<th>{{ __('Jour(s)') }}</th>
<th>%</th>
</tr>
</thead>
<tbody>
@php
$workdays = 260;
if (strpos($index, '2021') !== false) {
$workdays = 105;
}
$sum = 0;
@endphp
@foreach ($stat as $data)
<tr class="{{ Str::slug('celltype-'. \App\Enums\Planning\CellTypeEnum::make($data->activity->value)->label()) }}">
<td><b>{{ $data->activity->label() }}</b></td>
<td class="w-25"><b>{{ $data->nb / Config::get('schedules.daySlots') }}</b></td>
<td class="w-25">{{ round((($data->nb / Config::get('schedules.daySlots')) / $workdays) * 100) }}%</td>
</tr>
@php $sum += $data->nb; @endphp
@endforeach
<tr>
<td><b>{{ __('Total') }}</b></td>
<td class="w-25"><b>{{ $sum / Config::get('schedules.daySlots') }} / {{ $workdays }}</b></td>
<td class="w-25"><b>{{ round((($sum / 8) / $workdays) * 100) }}%</b></td>
</tr>
</tbody>
</table>
@endforeach
@endsection
@push('styles')
<style nonce="{{ $nonce }}">
@foreach(\App\Enums\Planning\CellTypeEnum::cases() as $type)
.celltype-{{ Str::slug($type->label()) }} {
background-color: {{ $type->color() }};
border: 1px solid black;
td {
background-color: inherit;
}
}
@endforeach
</style>
@endpush

View file

@ -2,6 +2,14 @@
<b>{{ $label }} {{ $required ? '*' : '' }}</b>
</label>
@php
if (old($name)) {
$model = $targetModel::find(old($name, []));
$modelSelected = [
'id' => $model->id,
'name' => $model->name,
'value' => $model->id,
];
}
$data = [
'readonly' => $readonly,
'name' => $name,

View file

@ -1,7 +1,7 @@
@if(!empty($label))
<label for="{{ $name }}" class="col-form-label"><b>{{ $label }}@if(!empty($required)) * @endif</b></label>
@endif
<select id="{{ $name }}" class="form-select" name="{{ $name }}" aria-describedby="{{ $id }}Help" @if(!empty($require))required @endif>
<select id="{{ $name }}" class="form-select" name="{{ $name }}" aria-describedby="{{ $id }}Help" @if(!empty($require))required @endif @if(!empty($disabled))disabled @endif>
@foreach ($associatedModels as $associatedModel)
<option value="{{ $associatedModel->value}}" @if (old($name, $model->{$name}->value ?? $value ?? Arr::first($associatedModels)->value) == $associatedModel->value) selected @endif>
{{ $associatedModel->label }}

View file

@ -68,11 +68,6 @@
</li>
@endcan
@endif
@can('isAdmin')
<li class="nav-item">
<a class="nav-link @if(Route::is('bo.schedule.*'))active @endif" href="{{ route('bo.schedule.index') }}">{{ __('Planning') }}</a>
</li>
@endcan
</ul>
@can('isAdmin')
<ul class="navbar-nav me-auto">
@ -109,11 +104,6 @@
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
@can('isDev')
<a class="dropdown-item" href="{{ route('bo.users.stats') }}">
{{ __('Statistiques') }}
</a>
@endcan
<a class="dropdown-item" href="{{ route('bo.users.passwordReset') }}">
{{ __('Identifiants') }}
</a>

View file

@ -25,11 +25,6 @@
show: "{{ route('bo.projects.show', ['project' => 'PROJECT']) }}",
"json-paginate": "{{ route('bo.projects.json-paginate') }}"
};
window.__SYSTEM._routes.schedule = {
index: "{{ route('bo.schedule.index') }}",
setCellContent: "{{ route('bo.schedule.setCellContent') }}",
eraseCellContent: "{{ route('bo.schedule.eraseCellContent') }}"
};
window.__SYSTEM._routes.invoices = {
"json-paginate": "{{ route('bo.invoices.json-paginate') }}",
};

View file

@ -66,10 +66,6 @@ Breadcrumbs::for('bo.users.edit', function (Generator $trail, User $user) {
$trail->parent('bo.users.index');
$trail->push(__('crud.actions.edit'), route('bo.users.edit', $user->id));
});
Breadcrumbs::for('bo.users.stats', function (Generator $trail) {
$trail->parent('bo.users.index');
$trail->push(__('crud.actions.stats'));
});
// * TICKET_TYPES
Breadcrumbs::for('bo.ticket_types.index', function (Generator $trail) {

View file

@ -12,7 +12,6 @@ use App\Http\Controllers\Bo\InvoiceController;
use App\Http\Controllers\Bo\LabelController;
use App\Http\Controllers\Bo\PackageController;
use App\Http\Controllers\Bo\ProjectController;
use App\Http\Controllers\Bo\ScheduleController;
use App\Http\Controllers\Bo\SiteController;
use App\Http\Controllers\Bo\TicketController;
use App\Http\Controllers\Bo\TicketTypeController;
@ -47,6 +46,7 @@ Route::middleware('admin-secure')
Route::middleware(['can:isAdmin'])
->group(function () {
// * RESOURCES
Route::resource('users', UserController::class)->except('show');
Route::resource('informations', InformationController::class)->except('show');
Route::resource('ticket_types', TicketTypeController::class)
->only(['index', 'edit', 'update'])
@ -68,10 +68,6 @@ Route::middleware('admin-secure')
->parameters(['credit_accounts' => 'account', 'credit_logs' => 'credit_log']);
Route::resource('labels', LabelController::class)->except('show');
// * USERS
Route::resource('users', UserController::class)->except('show');
Route::get('/users/stats', [UserController::class, 'stats'])->name('users.stats');
// * INVOICES
Route::get('/invoices/json/paginate', [InvoiceController::class, 'jsonPaginate'])
->name('invoices.json-paginate');
@ -94,27 +90,6 @@ Route::middleware('admin-secure')
Route::post('/ticket/{ticket}/create-issue', [TicketController::class, 'createGitlabIssue'])
->middleware('can:createGitlabIssue,ticket')
->name('ticket.create-issue');
// * SCHEDULE
Route::prefix('/schedule')
->name('schedule.')
->group(function () {
Route::get('/', [ScheduleController::class, 'index'])->name('index');
Route::post('/setCellContent', [ScheduleController::class, 'setCellContent'])
->name('setCellContent');
Route::post('/eraseCellContent', [
ScheduleController::class,
'eraseCellContent'
])
->name('eraseCellContent');
Route::get('/show-my-schedule', [ScheduleController::class, 'showMySchedule'])
->name('showMySchedule');
Route::get('/show-all-schedules', [
ScheduleController::class,
'showAllSchedules'
])
->name('showAllSchedules');
});
});
Route::resource('credit_accounts', AccountController::class)
->only(['index'])