Compare commits

..

2 commits

Author SHA1 Message Date
Jérémy Munsch
b44d3bb07a
implements #11 Ticket type templating 2023-04-19 17:35:57 +02:00
Jérémy Munsch
22a3f7bf27
npm audit fix 2023-04-19 15:52:10 +02:00
638 changed files with 35208 additions and 138158 deletions

View file

@ -1,5 +0,0 @@
# Browsers that we support
last 3 version
> 0.3%
not dead # no browsers without security updates

View file

@ -1,2 +0,0 @@
.git
*.md

View file

@ -3,9 +3,9 @@ root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
@ -13,9 +13,3 @@ trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4
[*.vue]
indent_size = 2

View file

@ -5,7 +5,6 @@ APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
@ -17,7 +16,6 @@ DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
@ -30,18 +28,7 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=noreply-sw@ldc.fr
MAIL_FROM_NAME="${APP_NAME}"
MAIL_LIST_SERVICEWEB=digital@ldc.fr
MAIL_ADMIN=digital@ldc.fr
MAIL_LIST_SERVICEWEB=equipe.web@ldc.fr
MAIL_ADMIN=equipe.web@ldc.fr
# Sentry
SENTRY_LARAVEL_DSN=
# UWIL ADMIN SECURE
# - LIST SEPARATED WITH ',' (Required)
UWIL_ADMIN_SECURE_CIDR_LIST=""
# - LIST SEPARATED WITH ',' (Optional)
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=
# GITLAB API
GITLAB_URL=
GITLAB_TOKEN=
LDC_STATUS_CHECKER_API_KEY=

View file

@ -35,21 +35,7 @@
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
"vue/script-indent": ["error", 2, { "baseIndent": 0 }],
"vue/one-component-per-file": "off"
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".vue", ".ts", ".d.ts"]
},
"alias": {
"extensions": [".vue", ".js", ".ts", ".scss", ".d.ts"],
"map": [
["~", "./resources/assets"],
["@", "./node_modules"]
]
}
}
}
}

14
.gitattributes vendored
View file

@ -1,11 +1,5 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore
.styleci.yml export-ignore

31
.gitignore vendored
View file

@ -1,28 +1,21 @@
.env
.env.backup
.env.production
.phpunit.result.cache
.stylelintcache
/.fleet
/.idea
/.phpunit.cache
/.vscode
/node_modules
/public/build
/public/cache
/public/hot
/public/sitemap.xml
/public/storage
/public/fichiers
/public/*
/storage/*.key
/vendor
auth.json
docker-compose.override.yml
.vscode
.env
.env.backup
.env.dev
.env.prod
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
storage/samlidp/*
.npmrc
.zshrc
.npm/
.composer/
package-lock.json
composer.lock
.idea/
auth.json

View file

@ -1,24 +1,118 @@
include:
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'lint/lint.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'security/security.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: "build/docker-multistage.yml"
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'test/test.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'deploy/deploy.yml'
stages:
- lint
- security
- build
- test
- deploy
- Security
- CodingStandards
- Builds
composer-security:
stage: Security
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
script:
- local-php-security-checker --format=json --path=./composer.lock > composer-security-checker-report.json
artifacts:
when: on_failure
expose_as: 'composer-security-checker'
paths:
- composer-security-checker-report.json
allow_failure: true
needs: []
npm-security:
stage: Security
image: node:17.9.1
before_script:
- npm install --location=global audit-ci@^6
script:
- audit-ci -p npm -o json > npm-security-checker-report.json
artifacts:
when: on_failure
expose_as: 'npm-security-checker'
paths:
- npm-security-checker-report.json
allow_failure: true
needs: []
phpcs:
stage: CodingStandards
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
before_script:
- composer config gitlab-token.gitlab.com $CI_DEPLOY_USER $CI_DEPLOY_PASSWORD
- composer install
cache:
paths:
- vendor/
script:
- vendor/bin/phpcs --report=junit --report-file=phpcs-report.xml
artifacts:
when: on_failure
expose_as: 'phpcs'
paths:
- phpcs-report.xml
reports:
junit: phpcs-report.xml
allow_failure: false
needs:
- composer-security
- npm-security
eslint:
stage: CodingStandards
image: node:17.9.1
before_script:
- npm install
cache:
paths:
- node_modules/
script:
- npm run eslint-ci --silent > eslint-report.xml
artifacts:
when: on_failure
expose_as: 'eslint'
paths:
- eslint-report.xml
reports:
junit: eslint-report.xml
allow_failure: false
needs:
- composer-security
- npm-security
stylelint:
stage: CodingStandards
image: node:17.9.1
before_script:
- npm install
cache:
paths:
- node_modules/
script:
- npm run stylelint-ci --silent > stylelint-report.json
artifacts:
when: on_failure
expose_as: 'stylelint'
paths:
- stylelint-report.json
allow_failure: false
needs:
- composer-security
- npm-security
front-build:
stage: Builds
image: node:17.9.1
before_script:
- npm install
cache:
paths:
- node_modules/
script:
- npm run prod > front-build-report.json
artifacts:
when: on_failure
expose_as: 'front-build-report'
paths:
- front-build-report.json
allow_failure: false
needs:
- phpcs
- eslint
- stylelint

13
.styleci.yml Normal file
View file

@ -0,0 +1,13 @@
php:
preset: laravel
disabled:
- no_unused_imports
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View file

@ -1,16 +1,8 @@
{
"extends": [
"stylelint-config-standard-scss",
"stylelint-config-sass-guidelines",
"stylelint-config-idiomatic-order"
],
"plugins": [
"stylelint-scss"
],
"files": [
"**/*.css",
"**/*.scss"
],
"ignoreFiles": [
"**/vendor/*.css",
"**/vendor/**/*.css",
@ -23,23 +15,10 @@
],
"customSyntax": "postcss-scss",
"rules": {
"declaration-property-value-no-unknown": true,
"indentation": 4,
"string-quotes": "double",
"selector-max-id": null,
"color-no-invalid-hex": true,
"order/properties-alphabetical-order": null,
"max-nesting-depth": 5,
"selector-max-compound-selectors": 5,
"declaration-colon-newline-after": null,
"value-list-comma-newline-after": null,
"scss/double-slash-comment-empty-line-before": [
"always",
{
"ignore": [
"between-comments",
"stylelint-commands",
"inside-block"
]
}
]
"order/properties-alphabetical-order": null
}
}

View file

@ -1,7 +1,3 @@
### 25.04.2023 -> 1.4.0
- Add ticket types management
- Fix AutoClose Jobs
- Fix Email notifications on private messages
### 04.2023 -> 1.3.0
- Update Status Enum
- Update TicketStatusChanged event

View file

@ -1,43 +0,0 @@
FROM composer:2.9.2 AS vendor
ARG GITLAB_USER
ARG GITLAB_PASSWORD
COPY database/ database/
COPY composer.json composer.json
COPY composer.lock composer.lock
RUN composer config gitlab-token.lab.s-web.fr ${GITLAB_USER} ${GITLAB_PASSWORD}
RUN composer install \
--ignore-platform-reqs \
--no-interaction \
--no-plugins \
--no-scripts \
--no-dev \
--prefer-dist
FROM node:20.18 AS frontend
ARG GITLAB_PASSWORD
RUN mkdir -p /app/public/build
COPY package*.json tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/
COPY resources/ /app/resources/
WORKDIR /app
RUN npm config set @uwil:registry https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/
RUN npm config set -- '//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
RUN npm ci --no-audit --no-fund --loglevel=error && npm run prod
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-production-8.5:${PHP_8_5_TAG}
ARG USER=www-data
COPY --chown=${USER}:${USER} . /app/srcs
COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
USER root
RUN chmod -R 755 /app/srcs/storage
USER ${USER}

View file

@ -1,17 +0,0 @@
# UWIL Support
PROD : support.uwil.fr
RE7 : support-web.re7-s-web.fr
## Gitlab Intégration
Le token de groupe pour l'intégration est valable 11 mois
Il faut le renouveler ici : https://lab.s-web.fr/groups/ldc-web/projets/-/settings/access_tokens et le nommer SW Intégration
Role : Guest
Scope : api
## TODO
- App\Models\Message:booted(): investigate why this make errors on prod,

File diff suppressed because it is too large Load diff

View file

@ -1,38 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Jobs\AutoCloseOldTickets as JobsAutoCloseOldTickets;
use Illuminate\Console\Command;
class AutoCloseOldTickets extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tickets:auto-close-old';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the AutoCloseOldTickets job.';
/**
* Execute the console command.
*
* @return integer
*/
public function handle()
{
$this->info('Running the Job, this could take some time.');
$job = new JobsAutoCloseOldTickets();
$job->handle();
$this->info('Job completed');
return 0;
}
}

View file

@ -2,9 +2,10 @@
namespace App\Console\Commands;
use App\Enums\Users\RoleEnum;
use App\Enums\Role;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
@ -15,7 +16,7 @@ class CreateUser extends Command
*
* @var string
*/
protected $signature = 'user:create';
protected $signature = 'create:user';
/**
* The console command description.
@ -24,34 +25,6 @@ class CreateUser extends Command
*/
protected $description = 'Create a user';
/**
* The name of the new user.
*
* @var string
*/
protected $name;
/**
* The email of the new user.
*
* @var string
*/
protected $email;
/**
* The password of the new user.
*
* @var string
*/
protected $password;
/**
* The role of the new user.
*
* @var integer
*/
protected $role;
/**
* Execute the console command.
*
@ -59,110 +32,59 @@ class CreateUser extends Command
*/
public function handle()
{
$this->name = null;
$this->addName();
$this->email = null;
$this->addEmail();
$this->password = null;
$this->addPassword();
$this->role = null;
$this->addRole();
$user = new User([
'name' => $this->name,
'email' => $this->email,
'password' => $this->password,
'role' => $this->role
]);
$user->saveOrFail();
$this->info('User created.');
return 0;
}
/**
* Add a firstname to the user.
*
* @return void
*/
private function addName(): void
{
while (is_null($this->name)) {
$email = null;
while (is_null($email)) {
try {
$tmp = $this->ask('Type the wanted user name', 'Admin');
$validator = Validator::make(['name' => $tmp], [
'name' => 'required|min:3|max:255'
]);
$validator->validate();
$this->name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add an email to the user.
*
* @return void
*/
private function addEmail(): void
{
while (is_null($this->email)) {
try {
$tmp = $this->ask('Type the wanted user email', 'digital@ldc.fr');
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,dns'
]);
$validator->validate();
$this->email = $tmp;
$email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
}
}
$name = null;
while (is_null($name)) {
try {
$tmp = $this->ask('Type the wanted user name', 'Admin');
$validator = Validator::make(['name' => $tmp], [
'name' => 'required|min:5,max:255'
]);
$validator->validate();
$name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
$password = null;
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
($password = $this->secret(
(!\is_null($password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
}
/**
* Add a password to the user.
*
* @return void
*/
private function addRole(): void
{
while (is_null($this->role)) {
$tmp = $this->confirm('Is the user an admin?');
$validator = Validator::make(['role' => $tmp], [
'role' => 'required|boolean'
]);
$validator->validate();
$this->role = $tmp ? RoleEnum::admin : RoleEnum::client;
};
$user = new User();
$user->name = $name;
$user->email = $email;
$user->role = $this->confirm('Is the user an admin?') ? Role::admin() : Role::client();
$user->password = Hash::make($password);
$user->saveOrFail();
$this->info('User created.');
return 0;
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Jobs\ExportTicketsState as JobsExportTicketsState;
use Illuminate\Console\Command;
class ExportTicketsState extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tickets:export-state';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Export the state of tickets to a CSV file';
/**
* Execute the console command.
*
* @return integer
*/
public function handle()
{
$this->info('Running the Job, this could take some time.');
$job = new JobsExportTicketsState();
$job->handle();
$this->info('Job completed');
return 0;
}
}

View file

@ -1,36 +0,0 @@
<?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 digital@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('digital@ldc.fr')->subject('Test Email');
});
return 0;
}
}

View file

@ -1,334 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Enums\Tickets\TicketStatusEnum;
use App\Models\GitlabIssues;
use App\Models\GitlabLabels;
use App\Models\GitlabMilestones;
use App\Models\GitlabProjects;
use App\Models\Ticket;
use Carbon\Carbon;
use GrahamCampbell\GitLab\Facades\GitLab;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class UpdateGitlabDatas extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:update-gitlab-datas {--is-cron}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Fetch some Gitlab datas';
/**
* Execute the console command.
*
* @return void
*/
public function handle(): void
{
$isCron = $this->option('is-cron');
$groupId = 23;
$this->updateProjectsByGroup($groupId);
$this->updateLabelsByGroup($groupId);
$projects = GitlabProjects::all();
foreach ($projects as $project) {
$this->updateMilestonesByProject($project);
$this->updateIssuesByProject($project, $isCron);
}
$this->syncTicketStatus();
}
/**
* updateProjectsByGroup function
*
* @param integer $groupId
* @return void
*/
private function updateProjectsByGroup(int $groupId): void
{
$allProjects = [];
$page = 1;
do {
$projects = GitLab::groups()->projects($groupId, [
'order_by' => 'created_at',
'sort' => 'asc',
'per_page' => 100,
'page' => $page,
'include_subgroups' => true
]);
$allProjects = array_merge($allProjects, $projects);
$page++;
} while (!empty($projects));
$this->info(sprintf(
"Fetched %d projects in group %d.",
count($allProjects),
$groupId
));
foreach ($allProjects as $project) {
GitlabProjects::upsert(
[
'name' => $project['name'],
'url' => $project['web_url'],
'is_archived' => $project['archived'],
'gitlab_id' => $project['id']
],
['gitlab_id']
);
}
$this->info("Gitlab Projects upsert complete.");
}
/**
* updateLabelsByGroup function
*
* @param integer $groupId
* @return void
*/
private function updateLabelsByGroup(int $groupId): void
{
$allLabels = [];
$page = 1;
do {
$labels = GitLab::groups()->labels($groupId, [
'per_page' => 100,
'page' => $page,
]);
$allLabels = array_merge($allLabels, $labels);
$page++;
} while (!empty($labels));
$this->info(sprintf(
"Fetched %d labels in group %d.",
count($allLabels),
$groupId
));
foreach ($allLabels as $project) {
GitlabLabels::upsert(
[
'name' => $project['name'],
'gitlab_id' => $project['id']
],
['gitlab_id']
);
}
$this->info("Gitlab labels upsert complete.");
}
/**
* updateIssuesByProject function
*
* @param GitlabProjects $project
* @param boolean $isCron
* @return void
*/
private function updateIssuesByProject(GitlabProjects $project, bool $isCron = false): void
{
$projectId = $project->gitlab_id;
$allIssues = [];
$page = 1;
do {
$params = [
'order_by' => 'created_at',
'sort' => 'asc',
'per_page' => 100,
'page' => $page,
'state' => 'all',
];
if ($isCron) {
$params = array_merge($params, ['updated_after' => now()->subDay()]);
}
$issues = GitLab::projects()->issues($projectId, $params);
$allIssues = array_merge($allIssues, $issues);
$page++;
} while (!empty($issues));
$this->info(sprintf(
"Fetched %d issues in project %d.",
count($allIssues),
$projectId
));
foreach ($allIssues as $issue) {
GitlabIssues::upsert(
[
'title' => $issue['title'],
'url' => $issue['web_url'],
'estimate_time' => $issue['time_stats']['time_estimate'] ?? 0,
'spent_time' => $issue['time_stats']['total_time_spent'] ?? 0,
'is_closed' => $issue['state'] === 'closed',
'gitlab_id' => $issue['id'],
'gitlab_iid' => $issue['iid'],
'milestone_iid' => $issue['milestone']['iid'] ?? null,
'gitlab_project_id' => $projectId,
'g_created_at' => Carbon::parse($issue['created_at'])->format('Y-m-d'),
'g_updated_at' => Carbon::parse($issue['updated_at'])->format('Y-m-d'),
'g_closed_at' => !is_null($issue['closed_at'])
? Carbon::parse($issue['closed_at'])->format('Y-m-d')
: null,
'g_due_date' => !is_null($issue['due_date'])
? Carbon::parse($issue['due_date'])->format('Y-m-d')
: null
],
['gitlab_id']
);
$issueModel = GitlabIssues::where('gitlab_id', $issue['id'])->firstOrFail();
$labelIds = [];
foreach ($issue['labels'] as $labelName) {
$label = Cache::remember("gitlab_label_{$labelName}", 300, function () use ($labelName) {
return GitlabLabels::where('name', $labelName)->first();
});
if ($label) {
$labelIds[] = $label;
}
}
$issueModel->gitlabLabels()->sync($labelIds);
}//end foreach
$this->info("Gitlab issues upsert complete.");
}
/**
* updateMilestonesByProject function
*
* @param GitlabProjects $project
* @return void
*/
private function updateMilestonesByProject(GitlabProjects $project): void
{
$projectId = $project->gitlab_id;
$allMilestones = [];
$page = 1;
do {
$milestones = [];
try {
$milestones = GitLab::milestones()->all($projectId, [
'per_page' => 100,
'page' => $page
]);
} catch (\Exception $e) {
if ($e->getCode() === 404) {
if ($project->delete()) {
Log::warning(sprintf(
"Project not found (%s), it has been deleted.",
$projectId
));
} else {
Log::warning(sprintf(
"Project not found (%s).",
$projectId
));
}
} else {
Log::error(sprintf(
'API Error for project %s, Message: %d',
$projectId,
$e->getMessage()
));
report($e);
}
}
$allMilestones = array_merge($allMilestones, $milestones);
$page++;
} while (!empty($milestones));
$this->info(sprintf(
"Fetched %d milestones in project %d.",
count($allMilestones),
$projectId
));
foreach ($allMilestones as $milestone) {
GitlabMilestones::upsert(
[
'title' => $milestone['title'],
'url' => $milestone['web_url'],
'is_closed' => $milestone['state'] === 'closed',
'gitlab_id' => $milestone['id'],
'gitlab_iid' => $milestone['iid'],
'gitlab_project_id' => $projectId,
'g_created_at' => Carbon::parse($milestone['created_at'])->format('Y-m-d'),
'g_updated_at' => Carbon::parse($milestone['updated_at'])->format('Y-m-d'),
'g_start_date' => !is_null($milestone['start_date'])
? Carbon::parse($milestone['start_date'])->format('Y-m-d')
: null,
'g_due_date' => !is_null($milestone['due_date'])
? Carbon::parse($milestone['due_date'])->format('Y-m-d')
: null
],
['gitlab_id']
);
}//end foreach
$this->info("Gitlab milestones upsert complete.");
}
/**
* Synchronizes ticket statuses based on GitLab issue labels.
*
* @return void
*/
private function syncTicketStatus(): void
{
$tickets = Ticket::whereNotNull('gitlab_issue')->whereNot('status', TicketStatusEnum::archived)->get();
foreach ($tickets as $ticket) {
$gitlabIssue = GitlabIssues::where('url', $ticket->gitlab_issue)->with('gitlabLabels')->first();
$gitlabIssueLabels = $gitlabIssue ? $gitlabIssue->gitlabLabels->pluck('name') : [];
if ($gitlabIssueLabels) {
match (true) {
$gitlabIssueLabels->contains('g-wip') => $this->updateTicketStatus($ticket, TicketStatusEnum::inDev),
$gitlabIssueLabels->contains('g-production') => $this->updateTicketStatus($ticket, TicketStatusEnum::production),
default => null,
};
}
}
}
/**
* Updates the ticket status only if it has changed.
*
* @param Ticket $ticket The ticket to update.
* @param TicketStatusEnum $status The new status to apply.
*
* @return void
*/
private function updateTicketStatus(Ticket $ticket, TicketStatusEnum $status): void
{
if ($ticket->status !== $status && $ticket->status !== TicketStatusEnum::waiting) {
$ticket->status = $status;
$ticket->saveOrFail();
}
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Jobs\WarnBeforeAutoCloseTicket as JobsWarnBeforeAutoCloseTicket;
use Illuminate\Console\Command;
class WarnBeforeAutoCloseTicket extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tickets:warn-before-auto-close';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the WarnBeforeAutoCloseTicket job.';
/**
* Execute the console command.
*
* @return integer
*/
public function handle()
{
$this->info('Running the Job, this could take some time.');
$job = new JobsWarnBeforeAutoCloseTicket();
$job->handle();
$this->info('Job completed');
return 0;
}
}

View file

@ -3,7 +3,6 @@
namespace App\Console;
use App\Jobs\AutoCloseOldTickets;
use App\Jobs\ExportTicketsState;
use App\Jobs\WarnBeforeAutoCloseTicket;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -25,10 +24,8 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
$schedule->job(ExportTicketsState::class)->weekly()->fridays()->at('14:00');
$schedule->job(AutoCloseOldTickets::class)->dailyAt('08:00');
$schedule->job(WarnBeforeAutoCloseTicket::class)->dailyAt('08:05');
$schedule->command('app:update-gitlab-datas --is-cron')->hourly()->between('7:00', '19:00');
}
/**

View file

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Dto;
final readonly class BrevoWebHookDto
{
public function __construct(
public readonly string $plainTextContent,
public readonly ?string $brand
) {}
}

View file

@ -0,0 +1,38 @@
<?php
namespace App\Enums\Credit;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self development()
* @method static self seo()
*/
class AccountType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'development' => 0,
'seo' => 1
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'development' => trans('Jours de développement'),
'seo' => trans('Jours de seo')
];
}
}

View file

@ -1,21 +0,0 @@
<?php
namespace App\Enums\Credit;
use App\Traits\Enums\BaseEnum;
enum AccountTypeEnum: int
{
use BaseEnum;
case development = 0;
case seo = 1;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::development->name => "Jours de développement",
self::seo->name => "Jours de seo",
];
}

View file

@ -0,0 +1,38 @@
<?php
namespace App\Enums\Credit;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self credit()
* @method static self debit()
*/
class BalanceOperation extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'credit' => 0,
'debit' => 1
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'credit' => trans('Créditer un compte'),
'debit' => trans('Débiter un compte')
];
}
}

View file

@ -1,21 +0,0 @@
<?php
namespace App\Enums\Credit;
use App\Traits\Enums\BaseEnum;
enum BalanceOperationEnum: int
{
use BaseEnum;
case credit = 0;
case debit = 1;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::credit->name => "Créditer un compte",
self::debit->name => "Débiter un compte",
];
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Enums\Credit;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self pending()
* @method static self accepted()
* @method static self refused()
*/
class EstimationStatus extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pending' => 0,
'accepted' => 1,
'refused' => 2
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pending' => trans('En attente'),
'accepted' => trans('Acceptée'),
'refused' => trans('Refusée'),
];
}
}

View file

@ -1,23 +0,0 @@
<?php
namespace App\Enums\Credit;
use App\Traits\Enums\BaseEnum;
enum EstimationStatusEnum: int
{
use BaseEnum;
case pending = 0;
case accepted = 1;
case refused = 2;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::pending->name => "En attente",
self::accepted->name => "Acceptée",
self::refused->name => "Refusée",
];
}

View file

@ -1,49 +0,0 @@
<?php
namespace App\Enums\Credit;
use App\Traits\Enums\BaseEnum;
enum EstimationTimeEnum: int
{
use BaseEnum;
case halfHour = 0.0625;
case hour = 0.125;
case hourAndHalf = 0.1875;
case twoHours = 0.25;
case twoHoursAndHalf = 0.3125;
case threeHours = 0.375;
case threeHoursAndHalf = 0.4375;
case fourHours = 0.5;
case fourHoursAndHalf = 0.5625;
case fiveHours = 0.625;
case fiveHoursAndHalf = 0.6875;
case sixHours = 0.75;
case sixHoursAndHalf = 0.8125;
case sevenHours = 0.875;
case sevenHoursAndHalf = 0.9375;
case eightHours = 1;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::halfHour->name => "Une demi-heure",
self::hour->name => "Une heure",
self::hourAndHalf->name => "Une heure et demie",
self::twoHours->name => "Deux heures",
self::twoHoursAndHalf->name => "Deux heures et demie",
self::threeHours->name => "Trois heures",
self::twoHoursAndHalf->name => "Trois heures et demie",
self::fourHours->name => "Quatre heures",
self::fourHoursAndHalf->name => "Quatre heures et demie",
self::fiveHours->name => "Cinq heures",
self::fiveHoursAndHalf->name => "Cinq heures et demie",
self::sixHours->name => "Six heures",
self::sixHoursAndHalf->name => "Six heures et demie",
self::sevenHours->name => "Sept heures",
self::sevenHoursAndHalf->name => "Sept heures et demi",
self::eightHours->name => "Huit heures",
];
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self collectionNeeds()
* @method static self specifications()
* @method static self quotation()
* @method static self tests()
* @method static self informations()
* @method static self feedback()
*/
class FileCategory extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'collectionNeeds' => 0,
'specifications' => 1,
'quotation' => 2,
'tests' => 3,
'informations' => 4,
'feedback' => 5
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'collectionNeeds' => trans('Recueil des besoins'),
'specifications' => trans('Cahier des charges'),
'quotation' => trans('Devis'),
'tests' => trans('Recettage'),
'informations' => trans('Informations'),
'feedback' => trans('Retours client'),
];
}
}

38
app/Enums/FileType.php Normal file
View file

@ -0,0 +1,38 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self stringlink()
* @method static self binfile()
*/
class FileType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'stringlink' => 0,
'binfile' => 1
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'stringlink' => trans('Lien uri'),
'binfile' => trans('Fichier hebergé')
];
}
}

90
app/Enums/InfoColors.php Normal file
View file

@ -0,0 +1,90 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self primary()
* @method static self secondary()
* @method static self success()
* @method static self danger()
* @method static self warning()
* @method static self info()
* @method static self light()
* @method static self dark()
* @method static self white()
*/
class InfoColors extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'primary' => 0,
'secondary' => 1,
'success' => 2,
'danger' => 3,
'warning' => 4,
'info' => 5,
'light' => 6,
'dark' => 7,
'white' => 8
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'primary' => trans('Primary'),
'secondary' => trans('Secondary'),
'success' => trans('Success'),
'danger' => trans('Danger'),
'warning' => trans('Warning'),
'info' => trans('Info'),
'light' => trans('Light'),
'dark' => trans('Dark'),
'white' => trans('white')
];
}
/**
* Enum classes
*
* @return array
*/
protected static function classes(): array
{
return [
'primary' => 'primary',
'secondary' => 'secondary',
'success' => 'success',
'danger' => 'danger',
'warning' => 'warning',
'info' => 'info',
'light' => 'light',
'dark' => 'dark',
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'class') {
return $this->classes()[$this->getDefinition()];
}
return parent::__get($attribute);
}
}

View file

@ -1,53 +0,0 @@
<?php
namespace App\Enums\Informations;
use App\Traits\Enums\BaseEnum;
enum InfoColorsEnum: int
{
use BaseEnum;
case primary = 0;
case secondary = 1;
case success = 2;
case danger = 3;
case warning = 4;
case info = 5;
case light = 6;
case dark = 7;
/**
* Optionnal labels definition
*/
private const CLASSES = [
self::primary->name => "primary",
self::secondary->name => "secondary",
self::success->name => "success",
self::danger->name => "danger",
self::warning->name => "warning",
self::info->name => "info",
self::light->name => "light",
self::dark->name => "dark",
];
/**
* Get Class
*
* @return string
*/
public function class(): string
{
return self::CLASSES[$this->name];
}
/**
* Get enum classes
*
* @return array<string>
*/
public static function toClasses(): array
{
return self::CLASSES;
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace App\Enums\Invoice;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self web()
* @method static self seo()
* @method static self package()
* @method static self arbitrary()
*/
class TaskType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'web' => 1,
'seo' => 2,
'package' => 3,
'arbitrary' => 4,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'web' => trans('Web'),
'seo' => trans('SEO'),
'package' => trans('Forfait'),
'arbitrary' => trans('Libre'),
];
}
/**
* Enum prices
*
* @return array<string,int>
*/
protected static function prices(): array
{
return [
'web' => 500,
'seo' => 690,
// * Linked to packages table
'package' => 0,
// * Arbitrary number
'arbitrary' => 0,
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'price') {
return $this->prices()[$this->getDefinition()];
}
return parent::__get($attribute);
}
}

View file

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

View file

@ -1,57 +0,0 @@
<?php
namespace App\Enums\Invoices;
use App\Traits\Enums\BaseEnum;
enum TaskTypeEnum: int
{
use BaseEnum;
case web = 1;
case seo = 2;
case package = 3;
case arbitrary = 4;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::web->name => "Web",
self::seo->name => "SEO",
self::package->name => "Forfait",
self::arbitrary->name => "Libre",
];
/**
* Custom added prices definition
*/
private const PRICES = [
self::web->name => 600,
self::seo->name => 690,
// * Linked to packages table
self::package->name => 0,
// * Arbitrary number
self::arbitrary->name => 0,
];
/**
* Get Price
*
* @return string
*/
public function price(): string
{
return self::PRICES[$this->name];
}
/**
* Get enum prices
*
* @return array<string>
*/
public static function toPrices(): array
{
return self::PRICES;
}
}

View file

@ -1,16 +0,0 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum ItemsPerPaginationEnum: int
{
use BaseEnum;
case five = 5;
case twelve = 12;
case twentytwo = 25;
case fifty = 50;
case onehundred = 100;
}

44
app/Enums/LoadLevel.php Normal file
View file

@ -0,0 +1,44 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self weak()
* @method static self stable()
* @method static self high()
* @method static self overload()
*/
class LoadLevel extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'weak' => 0,
'stable' => 1,
'high' => 2,
'overload' => 3
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'weak' => trans('Faible'),
'stable' => trans('Stable'),
'high' => trans('Élevée'),
'overload' => trans('Surcharge')
];
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace App\Enums\MacroSchedule;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self conception()
* @method static self development()
* @method static self recipe()
* @method static self production()
* @method static self meeting()
*/
final class Activity extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'conception' => 0,
'development' => 1,
'recipe' => 2,
'production' => 3,
'meeting' => 4
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'conception' => trans('Conception / Rédaction'),
'development' => trans('Développement'),
'recipe' => trans('Mise en recette'),
'production' => trans('Mise en production'),
'meeting' => trans('Réunion')
];
}
}

38
app/Enums/MessageType.php Normal file
View file

@ -0,0 +1,38 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self private()
* @method static self public()
*/
class MessageType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'private' => 1,
'public' => 2
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'private' => trans('Privé'),
'public' => trans('Public'),
];
}
}

View file

@ -1,21 +0,0 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum MessageTypeEnum: int
{
use BaseEnum;
case private = 1;
case public = 2;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::private->name => "Privé",
self::public->name => "Public",
];
}

View file

@ -0,0 +1,57 @@
<?php
namespace App\Enums\Planning;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* This is an enumeration class,
* the following methods are its possible values
*
* @method static self support()
* @method static self rnd()
* @method static self dev()
* @method static self projects()
* @method static self management()
* @method static self absence()
* @method static self leaddev()
*
*/
class ActivityType extends BaseEnumRoutable
{
/**
* Enum labels
*
* @return array
*/
public static function labels()
{
return [
'support' => trans('Support'),
'rnd' => trans('R&D'),
'dev' => trans('Développement/Facturable'),
'projects' => trans('Projets & Réunions'),
'management' => trans('Organisation management'),
'absence' => trans('Absence'),
'leaddev' => trans('Lead Dev')
];
}
/**
* Enum colors
*
* @return array
*/
public static function colors(): array
{
return [
'support' => '#c6e0b4',
'rnd' => '#b4c6e7',
'dev' => '#00b0f0',
'projects' => '#ffe699',
'management' => '#f4b084',
'absence' => '#aeaaaa',
'leaddev' => '#ff7979'
];
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\Enums\Planning;
use App\Models\Project;
use App\Models\Site;
use App\Models\Ticket;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* Décrit à quel modèle est lié
* la cellule
*
* @method static self ticket()
* \App\Models\Ticket
* @method static self site()
* \App\Models\Site
* @method static self project()
* \App\Models\Project
*/
class CellModel extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'ticket' => (new Ticket())->getTable(),
'site' => (new Site())->getTable(),
'project' => (new Project())->getTable(),
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'ticket' => trans('Ticket'),
'site' => trans('Site'),
'project' => trans('Projet')
];
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace App\Enums\Planning;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* Décrit le type d'activitée que représente
* la cellule
*
* @method static self support()
* @method static self rnd()
* @method static self dev()
* @method static self projects()
* @method static self management()
* @method static self absence()
* @method static self leaddev()
*/
class CellType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'support' => '#c6e0b4',
'rnd' => '#b4c6e7',
'dev' => '#00b0f0',
'projects' => '#ffe699',
'management' => '#f4b084',
'absence' => '#aeaaaa',
'leaddev' => '#ff7979'
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'support' => trans('Support'),
'rnd' => trans('R&D'),
'dev' => trans('Développement/Facturable'),
'projects' => trans('Projets & Réunions'),
'management' => trans('Organisation management'),
'absence' => trans('Absence'),
'leaddev' => trans('Lead Dev')
];
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace App\Enums\Planning;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* Décrit à quel moment de la journée appartient la cellule
*
* @method static self matin1()
* @method static self matin2()
* @method static self matin3()
* @method static self matin4()
* @method static self apresmidi1()
* @method static self apresmidi2()
* @method static self apresmidi3()
* @method static self apresmidi4()
*/
class TableRow extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'matin1' => 1,
'matin2' => 2,
'matin3' => 3,
'matin4' => 4,
'apresmidi1' => 5,
'apresmidi2' => 6,
'apresmidi3' => 7,
'apresmidi4' => 8,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'matin1' => trans('Matin 1'),
'matin2' => trans('Matin 2'),
'matin3' => trans('Matin 3'),
'matin4' => trans('Matin 4'),
'apresmidi1' => trans('Après-midi 1'),
'apresmidi2' => trans('Après-midi 2'),
'apresmidi3' => trans('Après-midi 3'),
'apresmidi4' => trans('Après-midi 4'),
];
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self new()
* @method static self specification()
* @method static self validated()
* @method static self development()
* @method static self recipe()
* @method static self production()
* @method static self finished()
*/
final class ProjectStatus extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'new' => 0,
'specification' => 1,
'validated' => 2,
'development' => 3,
'recipe' => 4,
'production' => 5,
'finished' => 6
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'new' => trans('Nouveau'),
'specification' => trans('Spécifications'),
'validated' => trans('Validé'),
'development' => trans('En développement'),
'recipe' => trans('En recettage'),
'production' => trans('En production'),
'finished' => trans('Terminé')
];
}
}

41
app/Enums/Role.php Normal file
View file

@ -0,0 +1,41 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self developper()
* @method static self admin()
* @method static self client()
*/
class Role extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'developper' => 0,
'admin' => 1,
'client' => 2
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'developper' => trans('Developpeur'),
'admin' => trans('Administrateur'),
'client' => trans('Client')
];
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self new()
* @method static self estimateTodo()
* @method static self takenInAccount()
* @method static self waiting()
* @method static self waitingValidation()
* @method static self recette()
* @method static self production()
* @method static self archived()
*/
class TicketStatus extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'new' => 1,
'estimateTodo' => 2,
'takenInAccount' => 3,
'waiting' => 4,
'waitingValidation' => 5,
'recette' => 6,
'production' => 7,
'archived' => 8,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'new' => trans('Nouveau'),
'estimateTodo' => trans('Devis à faire'),
'takenInAccount' => trans('Pris en compte'),
'waiting' => trans('En attente de réponse'),
'waitingValidation' => trans('En attente de validation'),
'recette' => trans('En recettage'),
'production' => trans('En production'),
'archived' => trans('Archivé'),
];
}
/**
* Enum classes
*
* @return array
*/
protected static function classes(): array
{
return [
'new' => 'text-danger',
'estimateTodo' => 'text-info',
'takenInAccount' => 'text-info',
'waiting' => 'text-white bg-danger',
'waitingValidation' => 'text-warning bg-danger',
'recette' => 'text-primary',
'production' => 'text-success',
'archived' => 'text-secondary',
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'class') {
return $this->classes()[$this->getDefinition()];
}
return parent::__get($attribute);
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self bug()
* @method static self evolution()
* @method static self br()
* @method static self newsletter()
* @method static self qrcode()
* @method static self advice()
*/
class TicketTypeEnum extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'bug' => 0,
'evolution' => 1,
'br' => 2,
'newsletter' => 3,
'qrcode' => 4,
'advice' => 5
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'bug' => trans('Bug'),
'evolution' => trans('Évolution'),
'br' => trans('Demande BR'),
'newsletter' => trans('Demande newsletter'),
'qrcode' => trans('Demande de QR Code'),
'advice' => trans('Conseil - Accompagnement')
];
}
/**
* Enum classes
*
* @return array
*/
protected static function classes(): array
{
return [
'bug' => 'bg-danger',
'evolution' => 'bg-success',
'br' => 'bg-primary',
'newsletter' => 'bg-warning',
'qrcode' => 'bg-secondary',
'advice' => 'bg-info'
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'class') {
return $this->classes()[$this->getDefinition()];
}
return parent::__get($attribute);
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace App\Enums\Tickets;
use App\Traits\Enums\BaseEnum;
enum TicketFileTypeEnum: int
{
use BaseEnum;
case png = '.png';
case jpeg = '.jpeg';
case jpg = '.jpg';
case pdf = '.pdf';
case pptx = '.pptx';
case docx = '.docx';
case xlsx = '.xlsx';
case csv = '.csv';
case svg = '.svg';
/**
* Optionnal labels definition
*/
private const LABELS = [
self::png->name => "Fichier png",
self::jpeg->name => "Fichier jpeg",
self::jpg->name => "Fichier jpg",
self::pdf->name => "Fichier pdf",
self::pptx->name => "Fichier pptx",
self::docx->name => "Fichier docx",
self::xlsx->name => "Fichier xlsx",
self::csv->name => "Fichier csv",
self::svg->name => "Fichier svg",
];
/**
* Custom added mimes type definition
*/
private const MIMETYPE = [
self::png->name => 'image/png',
self::jpeg->name => 'image/jpeg',
self::jpg->name => 'image/jpeg',
self::pdf->name => 'application/pdf',
self::pptx->name => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
self::docx->name => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
self::xlsx->name => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
self::csv->name => 'text/csv',
self::svg->name => 'image/svg+xml',
];
/**
* Get Class
*
* @return string
*/
public function mimetypes(): string
{
return self::MIMETYPE[$this->name];
}
/**
* Get enum classes
*
* @return array<string>
*/
public static function toMimetypes(): array
{
return self::MIMETYPE;
}
}

View file

@ -1,73 +0,0 @@
<?php
namespace App\Enums\Tickets;
use App\Traits\Enums\BaseEnum;
enum TicketStatusEnum: int
{
use BaseEnum;
case new = 1;
case estimateTodo = 2;
case takenInAccount = 3;
case waiting = 4;
case waitingValidation = 5;
case recette = 6;
case recetteValidated = 7;
case production = 8;
case archived = 9;
case inDev = 10;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::new->name => "Nouveau",
self::estimateTodo->name => "Étude chef(s) de projet",
self::takenInAccount->name => "À planifier",
self::waiting->name => "En attente de réponse",
self::waitingValidation->name => "En attente de validation",
self::recette->name => "En recettage",
self::recetteValidated->name => "Recettage validé",
self::production->name => "En production",
self::archived->name => "Archivé",
self::inDev->name => "En cours de développement",
];
/**
* Custom added classes definition
*/
private const CLASSES = [
self::new->name => 'text-danger',
self::estimateTodo->name => 'text-info',
self::takenInAccount->name => 'text-info',
self::waiting->name => 'text-white bg-danger',
self::waitingValidation->name => 'text-warning bg-danger',
self::recette->name => 'text-primary',
self::recetteValidated->name => 'text-success',
self::production->name => 'text-success',
self::archived->name => 'text-secondary',
self::inDev->name => 'text-discovery',
];
/**
* Get Class
*
* @return string
*/
public function class(): string
{
return self::CLASSES[$this->name];
}
/**
* Get enum classes
*
* @return array<string>
*/
public static function toClasses(): array
{
return self::CLASSES;
}
}

View file

@ -1,67 +0,0 @@
<?php
namespace App\Enums\Tickets;
use App\Traits\Enums\BaseEnum;
enum TicketTypeEnum: int
{
use BaseEnum;
case bug = 1;
case evolution = 2;
case br = 3;
case newsletter = 4;
case qrcode = 5;
case advice = 6;
case complaints = 7;
case mailing = 8;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::bug->name => "Bug",
self::evolution->name => "Évolution",
self::br->name => "Demande BR",
self::newsletter->name => "Demande newsletter",
self::qrcode->name => "Demande de QR Code",
self::advice->name => "Conseil - Accompagnement",
self::complaints->name => "Réclamations consommateurs",
self::mailing->name => "Demande mail en masse",
];
/**
* Custom added classes definition
*/
private const CLASSES = [
self::bug->name => "bg-danger",
self::evolution->name => "bg-success",
self::br->name => "bg-primary",
self::newsletter->name => "bg-warning",
self::qrcode->name => "bg-secondary",
self::advice->name => "bg-info",
self::complaints->name => "bg-dark",
self::mailing->name => "bg-discovery",
];
/**
* Get Class
*
* @return string
*/
public function class(): string
{
return self::CLASSES[$this->name];
}
/**
* Get enum classes
*
* @return array<string>
*/
public static function toClasses(): array
{
return self::CLASSES;
}
}

View file

@ -1,23 +0,0 @@
<?php
namespace App\Enums\Users;
use App\Traits\Enums\BaseEnum;
enum RoleEnum: int
{
use BaseEnum;
case developper = 0;
case admin = 1;
case client = 2;
/**
* Optionnal labels definition
*/
private const LABELS = [
self::developper->name => "Developpeur",
self::admin->name => "Administrateur",
self::client->name => "Client",
];
}

View file

@ -1,30 +0,0 @@
<?php
namespace App\Events;
use Domain\Models\ValueObjects\Email;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
readonly class BrevoWebhookCallReceived
{
use Dispatchable;
public function __construct(
public Email $from,
public Email $to,
public string $htmlRawContent
) {}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('default'),
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Events;
use App\Models\File;
use App\Models\User;
class FileCreated extends Loggable
{
/** @var \App\Models\File */
public $file;
/**
* Create a new event instance.
*
* @param \App\Models\File $file
* @param \App\Models\User $user
* @return void
*/
public function __construct(File $file, User $user)
{
parent::__construct($user);
$this->file = $file;
$this->message = trans(':username à crée le fichier :filename', [
'username' => $user->name,
'filename' => $file->name
]);
}
}

33
app/Events/Loggable.php Normal file
View file

@ -0,0 +1,33 @@
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
abstract class Loggable
{
use Dispatchable;
use SerializesModels;
/** @var \App\Models\User */
public $user;
/** @var string */
public $message = '';
/**
* Create a new event instance.
*
* @param \App\Models\User $user
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
$this->message = trans(':username did someting', [
'username' => $user->name
]);
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Events;
use App\Models\Project;
use App\Models\User;
class ProjectUpdated extends Loggable
{
/** @var \App\Models\Project */
public $project;
/**
* Create a new event instance.
*
* @param \App\Models\Project $project
* @param \App\Models\User $user
* @return void
*/
public function __construct(Project $project, User $user)
{
parent::__construct($user);
$this->project = $project;
$this->message = trans(':username à mis à jour projet :projectname', [
'username' => $user->name,
'projectname' => $project->name
]);
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Events;
use App\Models\Site;
use App\Models\User;
class SiteUpdated extends Loggable
{
/** @var \App\Models\Site */
public $site;
/**
* Create a new event instance.
*
* @param \App\Models\Site $site
* @param \App\Models\User $user
* @return void
*/
public function __construct(Site $site, User $user)
{
parent::__construct($user);
$this->site = $site;
$this->message = trans(':username à mis à jour site :sitename', [
'username' => $user->name,
'sitename' => $site->name
]);
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace App\Events;
use App\Models\Site;
use App\Models\Ticket;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TicketCreated implements ShouldQueue
{
use Dispatchable;
use SerializesModels;
public function __construct(
public readonly Site $siteReceivingTicket,
public readonly Ticket $ticket,
public readonly string $byUserName
) {}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('default'),
];
}
}

View file

@ -2,7 +2,7 @@
namespace App\Events;
use App\Enums\Tickets\TicketStatusEnum;
use App\Enums\TicketStatus;
use App\Models\Ticket;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
@ -21,7 +21,7 @@ class TicketStatusChanged
/** @var \App\Models\User */
public $user;
/** @var \App\Enums\Tickets\TicketStatusEnum */
/** @var \App\Enums\TicketStatus */
public $status;
/** @var string */
@ -36,17 +36,17 @@ class TicketStatusChanged
/**
* Create a new event instance.
*
* @param \App\Models\Ticket $ticket
* @param \App\Enums\Tickets\TicketStatusEnum $status
* @param \App\Models\User|null $user
* @param string|null $message
* @param boolean $is_private
* @param boolean $status_changed
* @param \App\Models\Ticket $ticket
* @param \App\Enums\TicketStatus $status
* @param \App\Models\User|null $user
* @param string|null $message
* @param boolean $is_private
* @param boolean $status_changed
* @return void
*/
public function __construct(
Ticket $ticket,
TicketStatusEnum $status,
TicketStatus $status,
User $user = null,
string $message = null,
bool $is_private = false,

View file

@ -6,27 +6,20 @@ use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
* @var array
*/
protected $dontReport = [];
protected $dontReport = [
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array<int, string>
* @var array
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
@ -16,6 +16,6 @@ class ForgotPasswordController extends Controller
*/
public function __construct()
{
$this->middleware('guest:backend');
$this->middleware('guest:web');
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
@ -24,6 +24,6 @@ class LoginController extends Controller
*/
public function __construct()
{
$this->middleware('guest:backend')->except('logout');
$this->middleware('guest:web')->except('logout');
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
@ -30,7 +30,7 @@ class ResetPasswordController extends Controller
'email' => 'required|email',
'password' => [
'required', 'confirmed', Password::min(8)
->mixedCase()
->mixedCase()
->letters()
->numbers()
->symbols()

View file

@ -1,186 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Brands\StoreBrandRequest;
use App\Http\Requests\Bo\Brands\UpdateBrandRequest;
use App\Models\Brand;
use App\Models\Site;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class BrandController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $brandModels */
$brandModels = Brand::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$brandModels,
$search,
null,
'name',
);
}
$searchFields = trans('Nom');
/** Sort columns with a query */
$this->sortQuery($brandModels);
/** Custom pagination */
$brandModels = $this->customPaginate($brandModels, $request->pagination);
return view('bo.pages.brands.index', compact('brandModels', 'search', 'searchFields'));
}
/**
* Show model action.
*
* @param \App\Models\Brand $brand
* @return \Illuminate\Contracts\View\View
*/
public function show(Brand $brand): \Illuminate\Contracts\View\View
{
return view('bo.pages.brands.show', ['brandModel' => $brand]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Brand $brand
* @return \Illuminate\Contracts\View\View
*/
public function create(Brand $brand): \Illuminate\Contracts\View\View
{
$siteModels = Site::orderBy('name', 'ASC')->get();
return view('bo.pages.brands.create', [
'brandModel' => $brand,
'siteModels' => $siteModels
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Brands\StoreBrandRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreBrandRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$brand = new Brand();
$brand->fill($request->safe()->except('sites'));
if ($brand->saveOrFail()) {
$brand->updateSites($brand);
return redirect()
->route('bo.brands.edit', $brand)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.brands'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.brands'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Brand $brand
* @return \Illuminate\Contracts\View\View
*/
public function edit(Brand $brand): \Illuminate\Contracts\View\View
{
$siteModels = Site::orderBy('name', 'ASC')->get();
return view('bo.pages.brands.edit', [
'brandModel' => $brand,
'siteModels' => $siteModels
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Brands\UpdateBrandRequest $request
* @param \App\Models\Brand $brand
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateBrandRequest $request, Brand $brand): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $brand) {
$brand->fill($request->safe()->except(['sites']));
$brand->updateSites($brand);
if ($brand->saveOrFail()) {
return redirect()
->route('bo.brands.edit', $brand)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.brands'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.brands'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Brand $brand
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Brand $brand): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($brand) {
if (count($brand->accounts)) {
return redirect()
->route('bo.brands.index')
->with('error', __(
':model n\'as pas pu être supprimé car elle est encore associée avec des Sites.',
['model' => Str::singular(trans('models.classes.brands'))]
));
} else {
$brand->deleteSites($brand);
if ($brand->deleteOrFail()) {
return redirect()
->route('bo.brands.index')
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.brands'))]
));
}
return redirect()
->back()
->with('error', __(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.brands'))]
));
} //end if
});
}
}

View file

@ -1,58 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Events\BrevoWebhookCallReceived;
use App\Http\Requests\Bo\BrevoWebhookRequest;
use Domain\Models\ValueObjects\Email;
use Psr\Log\LoggerInterface;
use Illuminate\Routing\Controller as BaseController;
use \Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
class BrevoWebhookController extends BaseController
{
public function __construct(
private readonly LoggerInterface $logger,
private readonly Dispatcher $dispatcher
) {}
public function webhookCallReceived(BrevoWebhookRequest $request): JsonResponse
{
$fromEmail = $request->getFromAddress();
$toEmail = $request->getToAddress();
$formContent = $request->getRawHtmlBody();
if (!$this->isFromLdc($fromEmail)) {
$this->logger->warning(
'Received a brevo webhook call from a non ldc email.',
[
'from' => $fromEmail,
'to' => $toEmail,
'content' => base64_encode($formContent)
]
);
return response()->json(
['ok' => false],
Response::HTTP_BAD_REQUEST
);
}
$this->dispatcher->dispatch(
new BrevoWebhookCallReceived(
from: $fromEmail,
to: $toEmail,
htmlRawContent: $formContent,
)
);
return response()->json(['ok' => true]);
}
private function isFromLdc(Email $email): bool
{
return substr(strrchr($email->value, "@"), 1) === 'ldc.fr';
}
}

View file

@ -1,166 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Companies\StoreCompanyRequest;
use App\Http\Requests\Bo\Companies\UpdateCompanyRequest;
use App\Models\Company;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class CompanyController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $companyModels */
$companyModels = Company::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$companyModels,
$search,
null,
'name'
);
}
$searchFields = trans('Nom');
/** Sort columns with a query */
$this->sortQuery($companyModels);
/** Custom pagination */
$companyModels = $this->customPaginate($companyModels, $request->pagination);
return view('bo.pages.companies.index', compact('companyModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Company $company
* @return \Illuminate\Contracts\View\View
*/
public function create(Company $company): \Illuminate\Contracts\View\View
{
return view('bo.pages.companies.create', [
'companyModel' => $company,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Companies\StoreCompanyRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreCompanyRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$company = new Company();
$company->fill($request->validated());
if ($company->saveOrFail()) {
return redirect()
->route('bo.companies.edit', $company)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.companies'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.companies'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Company $company
* @return \Illuminate\Contracts\View\View
*/
public function edit(Company $company): \Illuminate\Contracts\View\View
{
return view('bo.pages.companies.edit', [
'companyModel' => $company
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Companies\UpdateCompanyRequest $request
* @param \App\Models\Company $company
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateCompanyRequest $request, Company $company): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $company) {
$company->fill($request->validated());
if ($company->saveOrFail()) {
return redirect()
->route('bo.companies.edit', $company)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.companies'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.companies'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Company $company
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Company $company): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($company) {
if (count($company->invoices()->get()->toArray())) {
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_deleted_relationship',
[
'model' => Str::singular(trans('models.classes.companies')),
'relationship' => trans('models.classes.invoices')
]
));
}
if ($company->deleteOrFail()) {
return redirect()
->route('bo.companies.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.companies'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.companies'))]
));
});
}
}

View file

@ -1,107 +0,0 @@
<?php
namespace App\Http\Controllers\Bo\Credit;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\CreditAccounts\StoreCreditAccountRequest;
use App\Models\Brand;
use App\Models\Credit\Account;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class AccountController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \App\Models\User $user */
$user = auth()->user();
if (!$user->isAdmin()) {
$brandModels = $user->brands()->pluck('id');
} else {
$brandModels = $request->brand ?? Brand::all()->pluck('id');
}
/** @var \Illuminate\Database\Eloquent\Builder $credit_accountModels */
$credit_accountModels = Account::query()->whereIn('brand_id', $brandModels);
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$credit_accountModels,
$search,
null,
'label',
'balance',
);
}
$searchFields = trans('Label, solde');
/** Sort columns with a query */
$this->sortQuery($credit_accountModels);
/** Custom pagination */
$credit_accountModels = $this->customPaginate($credit_accountModels, $request->pagination);
return view('bo.pages.credit_accounts.index', compact(
'credit_accountModels',
'brandModels',
'search',
'searchFields'
));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Brand $brand
* @param \App\Models\Account $account
* @return \Illuminate\Contracts\View\View
*/
public function create(Brand $brand, Account $account): \Illuminate\Contracts\View\View
{
return view('bo.pages.credit_accounts.create', [
'brandModel' => $brand,
'credit_accountModel' => $account
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Models\Brand $brand
* @param \App\Http\Requests\Bo\CreditAccounts\StoreCreditAccountRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Brand $brand, StoreCreditAccountRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($brand, $request) {
$credit_accountModel = new Account();
$credit_accountModel->brand_id = $brand->id;
$credit_accountModel->fill($request->validated());
if ($credit_accountModel->saveOrFail()) {
return redirect()
->route('bo.brands.credit_accounts.index', $brand)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.accounts'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.accounts'))]
));
});
}
}

View file

@ -1,108 +0,0 @@
<?php
namespace App\Http\Controllers\Bo\Credit;
use App\Enums\Credit\BalanceOperationEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\CreditLogs\StoreCreditLogRequest;
use App\Models\Brand;
use App\Models\Credit\Account;
use App\Models\Credit\CreditLog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class CreditLogController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
$brandModel = $request->brand;
$credit_accountModel = $request->account;
/** @var \Illuminate\Database\Eloquent\Builder $credit_logModels */
$credit_logModels = CreditLog::query()->where('credit_account_id', $credit_accountModel->id);
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$credit_logModels,
$search,
null,
'amount',
);
}
$searchFields = trans('Montant');
/** Sort columns with a query */
$this->sortQuery($credit_logModels);
/** Custom pagination */
$credit_logModels = $this->customPaginate($credit_logModels, $request->pagination);
return view('bo.pages.credit_logs.index', compact(
'credit_logModels',
'credit_accountModel',
'brandModel',
'search',
'searchFields'
));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Brand $brand
* @param \App\Models\Account $account
* @return \Illuminate\Contracts\View\View
*/
public function create(Brand $brand, Account $account): \Illuminate\Contracts\View\View
{
return view('bo.pages.credit_logs.create', [
'brandModel' => $brand,
'credit_accountModel' => $account
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Models\Brand $brand
* @param \App\Models\Account $account
* @param \App\Http\Requests\Bo\CreditLogs\StoreCreditLogRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(
Brand $brand,
Account $account,
StoreCreditLogRequest $request
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($brand, $account, $request) {
$credit_logModel = new CreditLog();
$credit_logModel->credit_account_id = $request->account->id;
$credit_logModel->type = BalanceOperationEnum::credit;
$credit_logModel->fill($request->validated());
if ($credit_logModel->saveOrFail()) {
return redirect()
->route('bo.brands.credit_accounts.index', ['brand' => $brand, 'account' => $account])
->with('success', trans(
':model a été crédité',
['model' => Str::singular(trans('models.classes.accounts'))]
));
}
return redirect()
->back()
->with('error', trans(
':model ne peut être crédité',
['model' => Str::singular(trans('models.classes.accounts'))]
));
});
}
}

View file

@ -1,158 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Informations\StoreInformationRequest;
use App\Http\Requests\Bo\Informations\UpdateInformationRequest;
use App\Models\Information;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class InformationController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $informationModels */
$informationModels = Information::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$informationModels,
$search,
null,
'title',
'message'
);
}
$searchFields = trans('Titre, message');
/** Sort columns with a query */
$this->sortQuery($informationModels);
/** Custom pagination */
$informationModels = $this->customPaginate($informationModels, $request->pagination);
return view('bo.pages.informations.index', compact('informationModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Information $information
* @return \Illuminate\Contracts\View\View
*/
public function create(Information $information): \Illuminate\Contracts\View\View
{
return view('bo.pages.informations.create', [
'informationModel' => $information
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Informations\StoreInformationRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreInformationRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$information = new Information();
$information->fill($request->validated());
if ($information->saveOrFail()) {
return redirect()
->route('bo.informations.edit', $information)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.informations'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.informations'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Information $information
* @return \Illuminate\Contracts\View\View
*/
public function edit(Information $information): \Illuminate\Contracts\View\View
{
return view('bo.pages.informations.edit', [
'informationModel' => $information,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Informations\UpdateInformationRequest $request
* @param \App\Models\Information $information
* @return \Illuminate\Http\RedirectResponse
* @ignore phpcs:disable Generic.Files.LineLength.TooLong
*/
public function update(UpdateInformationRequest $request, Information $information): \Illuminate\Http\RedirectResponse
{
// phpcs:enable
return DB::transaction(function () use ($request, $information) {
$information->fill($request->validated());
if ($information->saveOrFail()) {
return redirect()
->route('bo.informations.edit', $information)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.informations'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.informations'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Information $information
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Information $information): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($information) {
if ($information->deleteOrFail()) {
return redirect()
->route('bo.informations.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.informations'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.informations'))]
));
});
}
}

View file

@ -1,227 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Invoices\InvoiceStatusEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Invoices\StoreInvoiceRequest;
use App\Http\Requests\Bo\Invoices\UpdateInvoiceRequest;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Package;
use App\Models\Site;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
class InvoiceController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
// If no sorting is requested, apply the default orderByDesc.
/** @var \Illuminate\Database\Eloquent\Builder $invoiceModels */
$invoiceModels = (!request()->query('sort_col') && !request()->query('sort_way'))
? Invoice::query()->orderByDesc('created_at')
: Invoice::query();
$status = intval($request->status);
switch ($status) {
case InvoiceStatusEnum::awaiting_signature->value:
$invoiceModels->whereNotNull('sent_client_date')->whereNull('signed_date');
break;
case InvoiceStatusEnum::awaiting_invoicing->value:
$invoiceModels->whereNotNull('sent_client_date')->whereNotNull('signed_date')->whereNull('sent_accounting_date');
break;
default:
if ($status !== 0) {
$invoiceModels->where('status', $status);
}
break;
}
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$invoiceModels,
$search,
null,
'firstname',
'lastname',
'ref',
'site_id',
);
}
$searchFields = trans('Nom, Prénom, Référence, Site');
/** Sort columns with a query */
$this->sortQuery($invoiceModels);
/** Custom pagination */
$invoiceModels = $this->customPaginate($invoiceModels, $request->pagination);
return view('bo.pages.invoices.index', compact('invoiceModels', 'search', 'searchFields', 'status'));
}
/**
* Show model action.
*
* @param \App\Models\Invoice $invoice
* @return \Illuminate\Contracts\View\View
*/
public function show(Invoice $invoice): \Illuminate\Contracts\View\View
{
return view('bo.pages.invoices.show', ['invoiceModel' => $invoice]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Invoice $invoice
* @return \Illuminate\Contracts\View\View
*/
public function create(Invoice $invoice): \Illuminate\Contracts\View\View
{
$packageModels = Package::where('published', true)->get();
$taskRowModel = Invoice::getTaskStructure();
$siteModels = Site::orderBy('name', 'ASC')->get();
$companyModels = Company::get();
return view('bo.pages.invoices.create', [
'invoiceModel' => $invoice,
'taskRowModel' => $taskRowModel,
'packageModels' => $packageModels,
'siteModels' => $siteModels,
'companyModels' => $companyModels,
'invoiceStatusArray' => $invoice->getInvoiceStatusArray($invoice),
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Invoices\StoreInvoiceRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreInvoiceRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$invoice = new Invoice();
$invoice->fill($request->validated());
if ($invoice->saveOrFail()) {
return redirect()
->route('bo.invoices.edit', $invoice)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.invoices'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.invoices'))]
));
});
}
/**
* Edit model action.
*
* @param Invoice $invoice
* @return \Illuminate\Contracts\View\View
*/
public function edit(Invoice $invoice): \Illuminate\Contracts\View\View
{
$packageModels = Package::get();
$taskRowModel = Invoice::getTaskStructure();
$siteModels = Site::orderBy('name', 'ASC')->get();
$companyModels = Company::get();
return view('bo.pages.invoices.edit', [
'invoiceModel' => $invoice,
'taskRowModel' => $taskRowModel,
'packageModels' => $packageModels,
'siteModels' => $siteModels,
'companyModels' => $companyModels,
'invoiceStatusArray' => $invoice->getInvoiceStatusArray($invoice),
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Invoices\UpdateInvoiceRequest $request
* @param \App\Models\Invoice $invoice
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateInvoiceRequest $request, Invoice $invoice): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $invoice) {
$invoice->fill(($invoice->status->value === InvoiceStatusEnum::opened->value) ? $request->validated() : []);
if ($invoice->saveOrFail()) {
return redirect()
->route('bo.invoices.edit', $invoice)
->with('success', __(
'crud.messages.has_been_updated',
['model' => __('models.classes.invoices')]
));
} //end if
return redirect()
->back()
->with('error', __(
'crud.messages.cannot_be_updated',
['model' => __('models.classes.invoices')]
));
});
}
/**
* Download Invoice.
*
* @param \App\Models\Invoice $invoice
* @return \Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function download(Invoice $invoice)
{
if (Gate::allows('isAdmin')) {
return $invoice->generateInvoice()
->stream(Str::slug(trans('Devis :appname :date - ref :ref', [
'appname' => config('app.name'),
'date' =>
$invoice->created_at !== $invoice->updated_at ?
$invoice->updated_at->isoFormat('YYYY-MM-DD') :
$invoice->created_at->isoFormat('YYYY-MM-DD'),
'ref' => $invoice->ref,
])) . '.pdf');
}
return redirect()->route('bo.invoices.index')
->with('error', __('crud.messages.cannot_be_updated', [
'model' => __('models.classes.invoices')
]));
}
/**
* Get a paginate resource.
*
* @param \Illuminate\Http\Request $request
* @return Illuminate\Http\JsonResponse
*/
public function jsonPaginate(Request $request): \Illuminate\Http\JsonResponse
{
$model = $request->targetModel;
return DB::transaction(function () use ($model) {
$models = $model::query()->orderBy('name', 'ASC')->paginate(12);
return \response()->json($models ?? 500);
});
}
}

View file

@ -1,157 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Labels\StoreLabelRequest;
use App\Http\Requests\Bo\Labels\UpdateLabelRequest;
use App\Models\Label;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class LabelController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $labelModels */
$labelModels = Label::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$labelModels,
$search,
null,
'name'
);
}
$searchFields = trans('Nom');
/** Sort columns with a query */
$this->sortQuery($labelModels);
/** Custom pagination */
$labelModels = $this->customPaginate($labelModels, $request->pagination);
return view('bo.pages.labels.index', compact('labelModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Label $label
* @return \Illuminate\Contracts\View\View
*/
public function create(Label $label): \Illuminate\Contracts\View\View
{
return view('bo.pages.labels.create', [
'labelModel' => $label
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Labels\StoreLabelRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreLabelRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$label = new Label();
$label->fill($request->validated());
if ($label->saveOrFail()) {
return redirect()
->route('bo.labels.edit', $label)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.labels'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.labels'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Label $label
* @return \Illuminate\Contracts\View\View
*/
public function edit(Label $label): \Illuminate\Contracts\View\View
{
return view('bo.pages.labels.edit', [
'labelModel' => $label,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Labels\UpdateLabelRequest $request
* @param \App\Models\Label $label
* @return \Illuminate\Http\RedirectResponse
* @ignore phpcs:disable Generic.Files.LineLength.TooLong
*/
public function update(UpdateLabelRequest $request, Label $label): \Illuminate\Http\RedirectResponse
{
// phpcs:enable
return DB::transaction(function () use ($request, $label) {
$label->fill($request->validated());
if ($label->saveOrFail()) {
return redirect()
->route('bo.labels.edit', $label)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.labels'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.labels'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Label $label
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Label $label): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($label) {
if ($label->deleteOrFail()) {
return redirect()
->route('bo.labels.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.labels'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.labels'))]
));
});
}
}

View file

@ -1,177 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Packages\StorePackageRequest;
use App\Http\Requests\Bo\Packages\UpdatePackageRequest;
use App\Models\Package;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class PackageController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $packageModels */
$packageModels = Package::query();
$status = $request->status;
if ($status === null) {
$packageModels->where('published', true);
} elseif ($status === '0') {
$packageModels->where('published', false);
} elseif ($status === '1') {
$packageModels->where('published', true);
}
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$packageModels,
$search,
null,
'name',
'amount'
);
}
$searchFields = trans('Nom, Montant');
/** Sort columns with a query */
$this->sortQuery($packageModels);
/** Custom pagination */
$packageModels = $this->customPaginate($packageModels, $request->pagination);
return view('bo.pages.packages.index', compact('packageModels', 'search', 'searchFields', 'status'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Package $package
* @return \Illuminate\Contracts\View\View
*/
public function create(Package $package): \Illuminate\Contracts\View\View
{
return view('bo.pages.packages.create', [
'packageModel' => $package,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Packages\StorePackageRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StorePackageRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$package = new Package();
$package->fill($request->validated());
if ($package->saveOrFail()) {
return redirect()
->route('bo.packages.edit', $package)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.packages'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.packages'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Package $package
* @return \Illuminate\Contracts\View\View
*/
public function edit(Package $package): \Illuminate\Contracts\View\View
{
return view('bo.pages.packages.edit', [
'packageModel' => $package,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Packages\UpdatePackageRequest $request
* @param \App\Models\Package $package
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdatePackageRequest $request, Package $package): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $package) {
$package->fill($request->validated());
if ($package->saveOrFail()) {
return redirect()
->route('bo.packages.edit', $package)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.packages'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.packages'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Package $package
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Package $package): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($package) {
if ($package->deleteOrFail()) {
return redirect()
->route('bo.packages.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.packages'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.packages'))]
));
});
}
/**
* Change model published.
*
* @param \App\Models\Package $package
* @return \Illuminate\Http\RedirectResponse
*/
public function changePublished(Package $package)
{
$package->update(['published' => !$package->getOriginal('published')]);
return redirect()->back()->with('success', trans(__('crud.messages.publish_status_saved')));
}
}

View file

@ -1,204 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Sites\StoreSiteRequest;
use App\Http\Requests\Bo\Sites\UpdateSiteRequest;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\Site;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class SiteController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \App\Models\User $user */
$user = Auth::user();
if ($user->isAdmin()) {
/** @var \Illuminate\Database\Eloquent\Builder $siteModels */
$siteModels = Site::query()->with(['invoices', 'tickets']);
} else {
/** @var \Illuminate\Database\Eloquent\Builder $siteModels */
$siteModels = Site::whereHas('users', function (\Illuminate\Database\Eloquent\Builder $query) use ($user) {
$query->where('id', $user->id)->with(['invoices', 'tickets']);
});
}
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$siteModels,
$search,
null,
'name',
);
}
$searchFields = trans('Nom');
/** Sort columns with a query */
$this->sortQuery($siteModels);
/** Custom pagination */
$siteModels = $this->customPaginate($siteModels, $request->pagination);
return view('bo.pages.sites.index', compact('siteModels', 'search', 'searchFields'));
}
/**
* Show model action.
*
* @param \App\Models\Site $site
* @return \Illuminate\Contracts\View\View
*/
public function show(Site $site): \Illuminate\Contracts\View\View
{
return view('bo.pages.sites.show', ['siteModel' => $site]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Site $site
* @return \Illuminate\Contracts\View\View
*/
public function create(Site $site): \Illuminate\Contracts\View\View
{
$userModels = User::orderBy('name', 'ASC')->get();
return view('bo.pages.sites.create', [
'siteModel' => $site,
'userModels' => $userModels
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Sites\StoreSiteRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreSiteRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$site = new Site();
$site->fill($request->validated());
if ($site->saveOrFail()) {
$site->users()->attach(collect($request->validated('users'))->pluck('id'));
return redirect()
->route('bo.sites.edit', $site)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.sites'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.sites'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Site $site
* @return \Illuminate\Contracts\View\View
*/
public function edit(Site $site): \Illuminate\Contracts\View\View
{
$userModels = User::orderBy('name', 'ASC')->get();
return view('bo.pages.sites.edit', [
'siteModel' => $site,
'userModels' => $userModels
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Sites\UpdateSiteRequest $request
* @param \App\Models\Site $site
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateSiteRequest $request, Site $site): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $site) {
$site->fill($request->validated());
$site->users()->sync(collect($request->validated('users'))->pluck('id'));
if ($site->saveOrFail()) {
return redirect()
->route('bo.sites.edit', $site)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.sites'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.sites'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Site $site
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Site $site): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($site) {
if ($site->deleteOrFail()) {
$site->users()->detach();
return redirect()
->route('bo.sites.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.sites'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.sites'))]
));
});
}
/**
* Get a paginate resource.
*
* @param \Illuminate\Http\Request $request
* @return Illuminate\Http\JsonResponse
*/
public function jsonPaginate(Request $request): \Illuminate\Http\JsonResponse
{
return DB::transaction(function () use ($request) {
$model = $request->targetModel;
$search = $request->search;
$models = $model::where("name", "LIKE", "%{$search}%")->orderBy("name", "ASC");
return ToolboxHelper::jsonPaginate($this->customPaginate($models, $request->pagination));
});
}
}

View file

@ -1,933 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Models\Site;
use App\Models\User;
use App\Models\Ticket;
use App\Mail\NewTicket;
use App\Models\Message;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Jobs\UpdateIssueLabels;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Support\Facades\DB;
use App\Events\NotifySubscriptions;
use App\Events\TicketStatusChanged;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\LinkTicket;
use Illuminate\Support\Facades\Mail;
use App\Enums\Tickets\TicketTypeEnum;
use App\Http\Requests\Bo\SortTickets;
use App\Http\Requests\Bo\StoreTicket;
use App\Http\Requests\Bo\UpdateTicket;
use App\Lib\Helpers\FileStorageHelper;
use App\Enums\Tickets\TicketStatusEnum;
use App\Events\TicketCreated;
use App\Http\Requests\Bo\CreateMessage;
use App\Http\Requests\Bo\UpdateMessage;
use Illuminate\Support\Facades\Session;
use GrahamCampbell\GitLab\Facades\GitLab;
use Illuminate\Database\Eloquent\Builder;
use App\Http\Requests\Bo\ChangeTicketStatus;
use App\Http\Requests\Bo\UpdateChangeOrderRequest;
use App\Http\Requests\Bo\AssignTicketToUserRequest;
use App\Http\Requests\Bo\UpdateTicketPriorityRequest;
use Illuminate\Contracts\Events\Dispatcher;
class TicketController extends Controller
{
/**
* TicketController
*/
public function __construct(
public readonly Dispatcher $dispatcher
) {
parent::__construct();
$this->authorizeResource(Ticket::class, 'ticket');
}
/**
* Tickets List
*
* @param SortTickets $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(SortTickets $request)
{
// * Handle Ajax
if ($request->ajax()) {
$tickets = Ticket::with(['user', 'assignedUser']);
if ($request->s) {
$tickets = $tickets->where('text', 'LIKE', "%$request->s%")
->orWhere('id', $request->s)
->orWhereHas('user', function ($q) use ($request) {
$q->where('name', 'LIKE', "%$request->s%");
});
}
/** @var \Illuminate/Pagination/LengthAwarePaginator $tickets */
$tickets = $tickets->paginate();
return \response()->json($tickets->getCollection()->map(function (Ticket $ticket) {
return [
'label' => sprintf(
'Ticket %d (%s): %s',
$ticket->id,
$ticket->user ? $ticket->user->name : trans('Utilisateur supprimé'),
Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35)
),
'value' => $ticket->id
];
}));
} //end if
/** @var \App\Models\User */
$user = auth()->user();
$tickets = Ticket::with(['site', 'labels']);
if (!$user->isAdmin()) {
$tickets->whereHas('site', function ($q) use ($user) {
$q->whereHas('users', function ($q) use ($user) {
$q->whereId($user->id);
});
});
$tickets->where('is_internal', false);
} else {
$tickets->has('site');
}
$tickets->where(
'status',
($request->st == TicketStatusEnum::archived->value) ? '=' : '<>',
TicketStatusEnum::archived
);
if (!$user->isAdmin() && $user->sites->count() === 1) {
$request->merge([
'pr' => $user->sites->first()->id,
]);
}
$tickets = $this->filterTickets($tickets, $request);
$tickets = $this->searchTicket($tickets, $request);
/** Sort columns with a query */
$this->sortQuery($tickets);
/** Custom pagination */
$tickets = $this->customPaginate($tickets, $request->pagination);
$sites = Site::select('id', 'name')->orderBy('name')->get();
$status = Arr::sort(TicketStatusEnum::cases(), function (TicketStatusEnum $value) {
return $value->label();
});
$types = Arr::sort(TicketTypeEnum::cases(), function (TicketTypeEnum $value) {
return $value->label();
});
return view('bo.pages.tickets.index', compact('tickets', 'sites', 'status', 'types'));
}
/**
* Show Ticket
*
* @param Request $request
* @param Ticket $ticket
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function show(Request $request, Ticket $ticket)
{
if ($request->ajax()) {
return \response()->json([
'label' => $ticket->formatName($ticket),
'value' => $ticket->id
]);
}
/** @var \App\Models\User */
$user = auth()->user();
if (!$user->can('update', $ticket)) {
abort(403);
}
if ($ticket->is_internal && !$user->can('changeInternalStatus', $ticket)) {
abort(403);
}
$messages = $ticket->messages()->orderBy('created_at', 'DESC')->get();
$isSubscribed = (bool)$ticket->subscriptions()->where('user_id', $user->id)->count();
return view('bo.pages.tickets.show', [
'ticket' => $ticket,
'messages' => $messages,
'isSubscribed' => $isSubscribed
]);
}
/**
* Show create Form
*
* @return \Illuminate\Http\RedirectResponse
*/
public function create()
{
/** @var \App\Models\User */
$user = auth()->user();
if ($user->isAdmin()) {
$sites = Site::orderBy('name')->get();
} else {
$sites = $user->sites;
}
if (!count($sites)) {
return redirect()->route('bo.sites.create')
->with('warning', trans('Vous devez tout d\'abord créer un site pour y ajouter un ticket'));
}
return view('bo.pages.tickets.create', compact('sites'));
}
/**
* Create ticket
*
* @param StoreTicket $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreTicket $request)
{
try {
return DB::transaction(function () use ($request) {
/** @var \App\Models\Site */
$site = Site::findOrFail($request->site_id);
/** @var \App\Models\User */
$user = auth()->user();
$ticket = new Ticket($request->validated());
/** @var array<\Illuminate\Http\UploadedFile> */
$fileTickets = $request->file_tickets ?? [];
if (count($fileTickets) > 0) {
foreach ($fileTickets as $key => $file) {
if ($key === 0) {
$ticket->file = FileStorageHelper::storeFile($ticket, $file);
} else {
$fileNumber = 'file' . $key + 1;
$ticket->$fileNumber = FileStorageHelper::storeFile($ticket, $file);
}
}
}
$ticket->user()->associate($user);
$ticket->site()->associate($site);
$ticket->saveOrFail();
if ($request->labels) {
$ticket->labels()->sync($request->labels);
}
$user->subscriptions()->toggle($ticket->id);
$this->dispatcher->dispatch(new TicketCreated(
siteReceivingTicket: $site,
ticket: $ticket,
byUserName: $user->name
));
return redirect()->route('bo.ticket.show', $ticket)
->with('success', trans('Ticket enregistré !'));
});
} catch (\ErrorException $e) {
report($e);
Session::flash('error', trans('Une erreur est survenue'));
} //end try
return \back();
}
/**
* Show update form
*
* @param Ticket $ticket
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function edit(Ticket $ticket)
{
/** @var \App\Models\User */
$user = auth()->user();
if ($user->isAdmin()) {
$sites = Site::orderBy('name')->get();
} else {
$sites = $user->sites;
}
return view('bo.pages.tickets.edit', compact('ticket', 'sites'));
}
/**
* Update Model
*
* @param UpdateTicket $request
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateTicket $request, Ticket $ticket)
{
$ticket->fill($request->validated());
try {
return DB::transaction(function () use ($request, $ticket) {
/** @var \App\Models\Site */
$site = Site::findOrFail($request->site_id);
/** @var null|array<\Illuminate\Http\UploadedFile> */
$fileTickets = $request->file_tickets;
if (isset($fileTickets) && count($fileTickets) > 0) {
foreach ($fileTickets as $key => $file) {
if ($key === 0) {
$ticket->file = FileStorageHelper::storeFile($ticket, $file);
} else {
$fileNumber = 'file' . $key + 1;
$ticket->$fileNumber = FileStorageHelper::storeFile($ticket, $file);
}
}
}
$ticket->labels()->sync($request->labels);
$ticket->site()->associate($site);
$ticket->saveOrFail();
return redirect()->route('bo.ticket.edit', $ticket->id)
->with('success', 'Modifications enregistrées !');
});
} catch (\ErrorException $e) {
report($e);
Session::flash('error', trans('Une erreur est survenue'));
} //end try
return \back();
}
/**
* Change ticket Status
*
* @param ChangeTicketStatus $request
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function changeStatus(ChangeTicketStatus $request, Ticket $ticket)
{
if ($ticket->gitlab_issue) {
\dispatch(new UpdateIssueLabels($ticket, $request->status));
}
$ticket->fill($request->validated());
$ticket->saveOrFail();
return back()
->with('warning', trans('Le status à été modifié'));
}
/**
* Add a new Message to the Ticket
*
* @param CreateMessage $request
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function createMessage(CreateMessage $request, Ticket $ticket)
{
try {
return DB::transaction(function () use ($request, $ticket) {
/** @var \Illuminate\Http\UploadedFile|null */
$filesMessage = $request->file_messages;
$message = new Message($request->validated());
$message->user()->associate(auth()->user());
$message->ticket()->associate($ticket);
/** @var \App\Models\User */
$user = auth()->user();
// Subscribe to ticket.
if ($request->subscribe) {
$user->subscriptions()->syncWithoutDetaching([$ticket->id]);
} else {
$user->subscriptions()->detach($ticket->id);
}
// Store files if any.
if (isset($filesMessage) and $filesMessage) {
foreach ($filesMessage as $key => $singleFile) {
if ($key === 0) {
$message->file = FileStorageHelper::storeFile($message, $singleFile);
$message->saveOrFail();
} else {
$fileNumber = 'file' . $key + 1;
$message->$fileNumber = FileStorageHelper::storeFile($message, $singleFile);
$message->saveOrFail();
}
}
}
$message->saveOrFail();
return back()
->with('success', trans('Votre message à été ajouté'));
}, 5);
} catch (\ErrorException $e) {
report($e);
Session::flash('error', trans('Une erreur est survenue'));
} //end try
return back();
}
/**
* Update Message
*
* @param UpdateMessage $request
* @param Ticket $ticket
* @param Message $message
* @return \Illuminate\Http\RedirectResponse
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function updateMessage(UpdateMessage $request, Ticket $ticket, Message $message)
{
$message->message = $request->message;
$message->saveOrFail();
return back()
->with('success', trans('Votre message à bien été modifié'));
}
/**
* Change ticket status to archived
*
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function setToArchive(Ticket $ticket)
{
$ticket->status = TicketStatusEnum::archived;
$ticket->saveOrFail();
return redirect()->route('bo.ticket.index')
->with('warning', trans('Le ticket est archivé'));
}
/**
* Subscribe to ticket
*
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function subscribe(Ticket $ticket)
{
/** @var \App\Models\User */
$user = auth()->user();
$user->subscriptions()->toggle($ticket->id);
if ($user->subscriptions()->where('ticket_id', $ticket->id)->exists()) {
return back()
->with('success', trans('Vous avez été inscrit'));
}
return back()->with('warning', trans('Vous avez été désinscrit'));
}
/**
* Reopen ticket
*
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function reopen(Ticket $ticket)
{
$message = trans(':ticketName à réouvert le ticket n°:ticketNumber', [
'ticketName' => $ticket->user->name,
'ticketNumber' => $ticket->id,
]);
/** @var \App\Enums\Tickets\TicketStatusEnum */
$oldStatus = $ticket->status;
$ticket->status = TicketStatusEnum::new;
$ticket->saveQuietly();
event(new TicketStatusChanged($ticket, $oldStatus, null, $message, false, false));
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
/** @var \App\Models\User */
$user = auth()->user();
$user->subscriptions()->toggle($ticket->id);
return back()->with('warning', trans('Votre ticket à été réouvert.'));
}
/**
* Assign ticket to user ticket
*
* @param AssignTicketToUserRequest $request
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function assignTo(AssignTicketToUserRequest $request, Ticket $ticket)
{
/** @var \App\Models\User */
$user = auth()->user();
/** @var \App\Models\User|null */
if ($assignedUser = User::whereId($request->assign)->first()) {
$message = trans(':admin a assigné le ticket n°:ticketNumber à :assignedAdmin', [
'admin' => $user->name,
'ticketNumber' => $ticket->id,
'assignedAdmin' => $assignedUser->name,
]);
$ticket->assignedUser()->associate($assignedUser);
} else {
$message = trans(
':admin a modifié l\'assignation du ticket n°:ticketNumber, il n\'est plus assigné à quelqu\'un.',
[
'admin' => $user->name,
'ticketNumber' => $ticket->id,
]
);
$ticket->assignedUser()->dissociate();
}
$ticket->saveQuietly();
/** @var \App\Enums\Tickets\TicketStatusEnum */
$oldStatus = $ticket->status;
// * Notification systeme doit etre privée.
event(new TicketStatusChanged($ticket, $oldStatus, null, $message, true, false));
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
/** @var \App\Models\User */
$user = auth()->user();
$user->subscriptions()->toggle($ticket->id);
if ($assignedUser) {
return back()->with('info', trans('Le ticket à été assigné à :assignedUser', [
'assignedUser' => $assignedUser->name
]));
}
return back()->with('info', trans('Le ticket n\'est plus assigné à personne'));
}
/**
* Link two tickets.
*
* @param LinkTicket $request
* @param Ticket $ticket
*
* @return \Illuminate\Http\RedirectResponse
*/
public function linkTicket(LinkTicket $request, Ticket $ticket)
{
$linkedTicket = Ticket::findOrFail($request->validated())->first();
if ($ticket->site_id !== $linkedTicket->site_id) {
return back()->with('error', trans('Le ticket que vous avez renseigné ne fait pas partie du même site.'));
}
if (!$ticket->linkedTickets()->where('linked_ticket_id', $linkedTicket->id)->exists()) {
$ticket->linkedTickets()->attach($linkedTicket->id);
$linkedTicket->linkedTickets()->attach($ticket->id);
return back()->with('success', trans('Les tickets ont été liés avec succès.'));
}
return back()->with('error', trans('Les tickets sont déjà liés ensemble.'));
}
/**
* Unlink two tickets.
*
* @param Ticket $ticket
* @param Ticket $linkedTicket
*
* @return \Illuminate\Http\RedirectResponse
*/
public function unlinkTicket(Ticket $ticket, Ticket $linkedTicket)
{
$ticket->linkedTickets()->detach($linkedTicket->id);
$linkedTicket->linkedTickets()->detach($ticket->id);
return back()->with('warning', trans('Le lien entre les tickets à été supprimé.'));
}
// * PRIVATES
/**
* Filter Tickets request constructor
*
* @param Builder $tickets
* @param Request $request
* @return Builder
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
private function filterTickets(Builder $tickets, Request $request): Builder
{
//phpcs:enable
if ($request->rst) {
Session::remove('tickets.ass');
Session::remove('tickets.st');
Session::remove('tickets.pr');
Session::remove('tickets.ty');
Session::remove('tickets.sort_col');
Session::remove('tickets.sort_way');
Session::remove('tickets.or');
Session::remove('tickets.search');
}
if ($request->assr) {
Session::remove('tickets.ass');
}
if ($request->prs) {
Session::remove('tickets.pr');
}
if ($request->sts) {
Session::remove('tickets.st');
}
if ($request->tys) {
Session::remove('tickets.ty');
}
if ($request->sort_col) {
Session::put('tickets.sort_col', $request->sort_col);
}
if ($request->sort_way) {
Session::put('tickets.sort_way', $request->sort_way);
}
if ($request->ord) {
Session::remove('tickets.or');
}
if ($request->pr) {
Session::put('tickets.pr', $request->pr);
}
if ($request->st) {
Session::put('tickets.st', $request->st);
}
if ($request->ty) {
Session::put('tickets.ty', $request->ty);
}
if ($request->ass) {
Session::put('tickets.ass', $request->ass);
}
if (Session::has('tickets.or')) {
Session::remove('tickets.ass');
Session::remove('tickets.st');
Session::remove('tickets.ty');
Session::remove('tickets.sort_col');
Session::remove('tickets.sort_way');
Session::remove('tickets.search');
}
if ($request->or && Session::has('tickets.pr')) {
Session::put('tickets.or', $request->or);
}
Session::save();
if (Session::has('tickets.pr')) {
$tickets->where('site_id', Session::get('tickets.pr'));
}
if (Session::has('tickets.ass')) {
$tickets->whereHas('assignedUser', function ($q) {
$q->whereId(Session::get('tickets.ass'));
});
}
if (Session::has('tickets.st')) {
$tickets->where('status', Session::get('tickets.st'));
}
if (Session::has('tickets.ty')) {
$tickets->where('type', Session::get('tickets.ty'));
}
if (Session::has('tickets.or')) {
$tickets->orderBy('order', 'ASC');
}
if (
Session::has('tickets.sort_col') and
Session::has('tickets.sort_way') and
in_array(Session::get('tickets.sort_col'), [
'type',
'title',
'assigned_to',
'date_mep',
'created_at',
'created_by'
])
) {
$tickets->orderBy(
Session::get('tickets.sort_col'),
Session::get('tickets.sort_way')
);
} else {
$tickets->orderBy('last_activity', 'DESC');
}
return $tickets;
}
/**
* Search ticket sql request constructor
*
* @param Builder $tickets
* @param Request $request
* @return Builder
*/
private function searchTicket(Builder $tickets, Request $request): Builder
{
if ($request->exists('search')) {
if (empty($request->search)) {
Session::remove('tickets.search');
} else {
Session::put('tickets.search', $request->search);
}
}
if (!Session::has('tickets.search')) {
return $tickets;
}
return $this->searchQuery($tickets, Session::get('tickets.search'), function ($q) {
$words = explode(' ', Session::get('tickets.search'));
if (\is_array($words)) {
foreach ($words as $word) {
if (!strlen($word)) {
continue;
}
$s = collect(TicketStatusEnum::cases())->filter(function (TicketStatusEnum $t) use ($word) {
return Str::slug($t->label()) === Str::slug($word);
})->map(function (TicketStatusEnum $ticket) {
return $ticket->value;
});
$t = collect(TicketTypeEnum::cases())->filter(function (TicketTypeEnum $s) use ($word) {
return Str::slug($s->label()) === Str::slug($word);
})->map(function (TicketTypeEnum $ticket) {
return $ticket->value;
});
$q->orWhere('id', $word)
->orWhereIn('status', $s)
->orWhereIn('type', $t)
->orWhereHas('user', function ($q) use ($word) {
$q->where('name', 'LIKE', "%$word%");
});
} //end foreach
} //end if
}, 'text', 'title', 'file', 'file2', 'file3', 'file4', 'file5');
}
/**
* Get a paginate resource.
*
* @param \Illuminate\Http\Request $request
* @return Illuminate\Http\JsonResponse
*/
public function jsonPaginate(Request $request): \Illuminate\Http\JsonResponse
{
return DB::transaction(function () use ($request) {
$model = $request->targetModel;
$search = $request->search;
// * Handle Ajax
$tickets = $model::with(['user', 'assignedUser']);
if ($search) {
$tickets = $tickets->where('text', 'LIKE', "%$search%")
->orWhere('id', $search)
->orWhereHas('user', function ($q) use ($search) {
$q->where('name', 'LIKE', "%$search%");
})
->orderBy("id", "ASC");
}
return ToolboxHelper::jsonPaginate(
$this->customPaginate($tickets, $request->pagination),
'formatName'
);
});
}
/**
* Create a gitlab issue from a ticket.
*
* @param \App\Models\Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function createGitlabIssue(Ticket $ticket)
{
$userGitlabId = auth()->user()->gitlab_id;
$gitlabRepoId = $ticket->site->gitlab_project_id;
if (GitLab::projects()->show($gitlabRepoId)['archived']) {
return back()->with('error', \trans("Ce projet GitLab est archivé et ne peut pas être utilisé."));
}
$description = \sprintf("%s<br>", $ticket->text);
$cLabel = match ($ticket->type) {
TicketTypeEnum::bug => 'c-bug,g-to-dispatch',
TicketTypeEnum::evolution,
TicketTypeEnum::mailing => 'c-feature',
TicketTypeEnum::newsletter => 'c-feature,g-to-estimate',
};
$assigneeId = match ($ticket->type) {
TicketTypeEnum::bug => $userGitlabId,
TicketTypeEnum::evolution,
TicketTypeEnum::mailing,
TicketTypeEnum::newsletter => $ticket->site->projectManager?->gitlab_id
};
try {
foreach ($ticket->getFiles() as $key => $file) {
if (!\is_null($file)) {
$uploadedFile = GitLab::projects()->uploadFile($gitlabRepoId, $file);
$description .= $this->formatGitlabFiles($ticket, $uploadedFile, $key + 1);
}
}
$data = [
'title' => sprintf(
'ts-%s : %s',
$ticket->id,
$ticket->title
),
'description' => $description,
'labels' => \implode(",", [
'c-support',
$cLabel
]),
];
if ($assigneeId) {
$data['assignee_id'] = $assigneeId;
}
$issue = GitLab::issues()->create($gitlabRepoId, $data);
} catch (\Exception $e) {
\report($e);
return back()->with('error', \trans("Une erreur est survenue"));
} catch (\Throwable $e) {
\report($e);
return back()->with('error', \trans("Une erreur est survenue"));
} //end try
$ticket->gitlab_issue = $issue['web_url'];
$ticket->save();
return back()->with(
'success',
trans("L'issue a été créée sur Gitlab.")
);
}
/**
* Format files for Gitlab issues
*
* @param \App\Models\Ticket $ticket
* @param array $uploadedFile
* @param integer $fileNb
* @return string
*/
private function formatGitlabFiles(Ticket $ticket, array $uploadedFile, int $fileNb)
{
if (Str::endsWith($ticket->file, ['jpg', 'png', 'jpeg', 'pptx', 'docx', 'xslx', 'csv', 'svg'])) {
$file = \sprintf(
"![Fichier joint %d](%s)",
$fileNb,
$uploadedFile['url']
);
} else {
$file = \sprintf(
'<a href="%s" target="_blank">%s</a>',
$uploadedFile['url'],
__('Lien vers le fichier'),
);
}
return \sprintf("<br><br> Fichier %d : <br>%s<br>", $fileNb, $file);
}
/**
* Change order.
*
* @param \App\Http\Requests\Bo\UpdateChangeOrderRequest $request
* @param mixed ...$params
* @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au params utilisé.
*/
public function changeOrder(UpdateChangeOrderRequest $request, mixed ...$params): \Illuminate\Http\RedirectResponse
{
$paramNames = collect(request()->route()->parameterNames);
$className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly();
$className = "\\App\\Models\\{$className}";
$model = Arr::get($params, $paramNames->count() - 2);
if (!class_exists($className)) {
throw new \RuntimeException("The {$className} class doesn't exist.");
}
if (!\is_object($model)) {
throw new \RuntimeException("The second last param isn't an object.");
}
if (get_class($model) !== (string)Str::of($className)->replaceStart('\\', '')) {
throw new \RuntimeException("The {$className} doesn't match second last binded param.");
}
/** @var \Illuminate\Database\Eloquent\Model|null */
$model = $className::query()->where($model->getRouteKeyName(), $model->getRouteKey())->first();
if (!$model) {
return back()->with('error', trans('crud.messages.order_not_changed'));
}
return DB::transaction(function () use ($request, $className, $model) {
/** @var \Illuminate\Support\Collection $models */
$models = $className::query()
->orderBy('order', 'ASC')
->where('site_id', $model->site_id)
->where('status', '!=', TicketStatusEnum::archived)
->where('id', '!=', $model->getKey())
->where('order', $request->direction ? '<' : '>', $model->order)
->get();
/** @var \Illuminate\Database\Eloquent\Model|null $tmp */
$tmp = $request->direction ? $models->last() : $models->first();
if (!$tmp) {
return back()->with('error', trans('crud.messages.order_not_changed'));
}
$newOrder = $tmp->getOriginal('order');
$tmp->update(['order' => $model->getOriginal('order')]);
$model->update(['order' => $newOrder]);
return redirect()->back()->with('success', trans('crud.messages.order_changed'));
});
}
/**
* Update ticket priority.
*
* @param UpdateTicketPriorityRequest $request
* @param Ticket $ticket
* @return void
*/
public function changePriority(UpdateTicketPriorityRequest $request, Ticket $ticket)
{
$newPriority = $request->validated('priority');
return DB::transaction(function () use ($ticket, $newPriority) {
// Update all tickets who will be moved with this priority update.
$request = Ticket::query()
->where('site_id', $ticket->site_id)
->whereNot('status', TicketStatusEnum::archived)
->whereNot('id', $ticket->id)
->whereBetween('order', [min($ticket->order, $newPriority), max($ticket->order, $newPriority)]);
$ticket->order < $newPriority ? $request->decrement('order', 1) : $request->increment('order', 1);
// Update the ticket.
$ticket->order = $newPriority;
if (!$ticket->saveOrFail()) {
return back()->with('error', trans('crud.messages.order_not_changed'));
};
return redirect()->back()->with('success', trans('crud.messages.order_changed'));
});
}
}

View file

@ -1,81 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\TicketTypes\UpdateTicketTypeRequest;
use App\Models\Ticket\TicketType;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class TicketTypeController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $ticketTypeModels */
$ticketTypeModels = TicketType::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$ticketTypeModels,
$search,
null,
'type',
);
}
$searchFields = trans('Type');
/** Sort columns with a query */
$this->sortQuery($ticketTypeModels);
/** Custom pagination */
$ticketTypeModels = $this->customPaginate($ticketTypeModels, $request->pagination);
return view('bo.pages.ticket_types.index', compact('ticketTypeModels', 'search', 'searchFields'));
}
/**
* Show the form for editing the specified resource.
*
* @param App\Models\Ticket\TicketType $ticketType
* @return \Illuminate\Contracts\View\View
* @ignore phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function edit(TicketType $ticketType): \Illuminate\Contracts\View\View
{
return view('bo.pages.ticket_types.edit', ['ticketTypeModel' => $ticketType]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\TicketTypes\UpdateTicketTypeRequest $request
* @param \App\Models\Ticket\TicketType $ticketType
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateTicketTypeRequest $request, TicketType $ticketType): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $ticketType) {
$ticketType->fill($request->validated());
if ($ticketType->saveOrFail()) {
return redirect()->route('bo.ticket_types.edit', $ticketType->id)
->with('success', __('crud.messages.has_been_updated', [
'model' => trans_choice('models.classes.ticket_types', 1)
]));
}
return redirect()->route('bo.ticket_types.edit', $ticketType->id)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => trans_choice('models.classes.ticket_types', 1)
]));
});
}
}

View file

@ -1,232 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Models\Site;
use App\Models\User;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Users\StoreUserRequest;
use App\Http\Requests\Bo\Users\UpdateUserRequest;
use App\Http\Controllers\Bo\Auth\ForgotPasswordController;
use App\Http\Requests\Bo\Users\FavouriteSiteRequest;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $userModels */
$userModels = User::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$userModels,
$search,
null,
'name',
'email'
);
}
$searchFields = trans('Label, email');
/** Sort columns with a query */
$this->sortQuery($userModels);
/** Custom pagination */
$userModels = $this->customPaginate($userModels, $request->pagination);
return view('bo.pages.users.index', compact('userModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function create(User $user): \Illuminate\Contracts\View\View
{
$siteModels = Site::orderBy('name', 'ASC')->get();
return view('bo.pages.users.create', [
'userModel' => $user,
'siteModels' => $siteModels
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Users\StoreUserRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreUserRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$user = User::query()->onlyTrashed()->where('email', $request->validated('email'))->first() ?? new User();
$user->fill($request->safe()->except('sites'));
$user->deleted_at = null;
if ($user->saveOrFail()) {
$user->sites()->attach(collect($request->validated('sites'))->pluck('id'));
return redirect()
->route('bo.users.edit', $user)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.users'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.users'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function edit(User $user): \Illuminate\Contracts\View\View
{
$siteModels = Site::orderBy('name', 'ASC')->get();
return view('bo.pages.users.edit', [
'userModel' => $user,
'siteModels' => $siteModels
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Users\UpdateUserRequest $request
* @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $user) {
$user->fill($request->safe()->except(['sites']));
$user->sites()->sync(collect($request->validated('sites'))->pluck('id'));
if ($user->saveOrFail()) {
return redirect()
->route('bo.users.edit', $user)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.users'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.users'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(User $user): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($user) {
if ($user->deleteOrFail()) {
$user->sites()->detach();
return redirect()
->route('bo.users.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.users'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.users'))]
));
});
}
/**
* Password reset form
*
* @return \Illuminate\Contracts\View\View
*/
public function settings(): \Illuminate\Contracts\View\View
{
return view('bo.pages.users.settings');
}
/**
* Favourite site submit
*
* @param \App\Http\Requests\Bo\Users\FavouriteSiteRequest $request
*
* @return \Illuminate\Http\RedirectResponse
*/
public function favouriteSiteSubmit(FavouriteSiteRequest $request): \Illuminate\Http\RedirectResponse
{
/** @var \App\Models\User */
$user = auth()->user();
try {
$user->favourite_site_id = $request->favourite_site_id;
$user->saveOrFail();
return redirect()->back()->with("success", trans('crud.messages.favourite_site_changed'));
} catch (\Exception $e) {
\report($e);
return redirect()->back()->with('error', trans('crud.messages.favourite_site_not_changed'));
} catch (\Throwable $e) {
\report($e);
return redirect()->back()->with('error', trans('crud.messages.favourite_site_not_changed'));
}
}
/**
* Password reset submit
*
* @return \Illuminate\Http\RedirectResponse
*/
public function passwordResetSubmit(): \Illuminate\Http\RedirectResponse
{
/** @var \App\Models\User */
$user = auth()->user();
$request = new Request(['email' => $user->email]);
return app(ForgotPasswordController::class)->sendResetLinkEmail($request);
}
/**
* Get february last day date of the year
*
* @param integer $year
* @return integer 28 or 29
*/
private function getLastDayFebruary(int $year): int
{
if ((is_int($year / 4) && !is_int($year / 100)) || is_int($year / 400)) {
return 29;
}
return 28;
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers;
use App\Models\Brand;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class BrandController extends Controller
{
use CrudController {
__construct as __CrudConstructor;
index as crudIndex;
}
/** @var Account */
public static $modelClass = Brand::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* ProjectController
*/
public function __construct()
{
parent::__construct();
$this->__CrudConstructor();
$this->authorizeResource(Brand::class, 'brand');
}
/**
* Crud index view
*
* @param \Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(CrudIndexRequest $request)
{
/** @var \Illuminate\View\View */
$view = $this->crudIndex($request);
if ($request->ajax()) {
return $view;
}
return view('brands.index', $view->getData());
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Company;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class CompanyController extends Controller
{
use CrudController;
/** @var \App\Models\MacroSchedule\Marker */
public static $modelClass = Company::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
}

View file

@ -2,20 +2,17 @@
namespace App\Http\Controllers;
use App\Enums\Tickets\TicketStatusEnum;
use App\Models\Ticket;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use App\Enums\TicketStatus;
use App\Models\Ticket;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
class Controller extends BaseController
{
@ -42,17 +39,17 @@ class Controller extends BaseController
55,
];
$ticketsNew = Ticket::where('status', TicketStatusEnum::new);
$ticketsWaiting = Ticket::where('status', TicketStatusEnum::waiting);
$ticketsNew = Ticket::where('status', TicketStatus::new());
$ticketsWaiting = Ticket::where('status', TicketStatus::waiting());
$ticketsInProgress = Ticket::whereNotIn(
'status',
[
TicketStatusEnum::new,
TicketStatusEnum::estimateTodo,
TicketStatusEnum::waiting,
TicketStatusEnum::production,
TicketStatusEnum::archived,
TicketStatusEnum::takenInAccount
TicketStatus::new(),
TicketStatus::estimateTodo(),
TicketStatus::waiting(),
TicketStatus::production(),
TicketStatus::archived(),
TicketStatus::takenInAccount()
]
);
@ -76,13 +73,12 @@ class Controller extends BaseController
* Build Search query on specified fields
* splitting search into words or using whole sentence
*
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields
* would be concatenate to make the search.
* @return \Illuminate\Database\Eloquent\Builder
* @param Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields would be concatenate to make the search.
* @return Builder
*/
protected function searchQuery(
Builder $query,
@ -113,83 +109,28 @@ class Controller extends BaseController
/**
* Build query search on all fields
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param array $fields
* @return \Illuminate\Database\Eloquent\Builder
* @param Builder $query
* @param string $search
* @param array $fields
* @return Builder
*/
private function searchOnFields(
Builder $query,
string $search,
array $fields
): \Illuminate\Database\Eloquent\Builder {
private function searchOnFields(Builder $query, string $search, array $fields): Builder
{
$table = $query->getModel()->getTable();
foreach ($fields as $field) {
$field = str_replace('?', '\?', $field);
if (is_array($field)) {
foreach ($fields as $f) {
$f = str_replace('?', '\?', $f);
if (is_array($f)) {
$query->orWhereRaw(
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($field)->map(function ($q) use ($table) {
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($f)->map(function ($q) use ($table) {
return "`$table`.`$q`";
})->implode(', \' \', ')),
['%' . htmlspecialchars($search) . '%']
["%$search%"]
);
return $query;
} else {
$query->orWhereRaw("UPPER(`$table`.`$f`) LIKE UPPER(?)", ["%$search%"]);
}
// Get models woth enum's label.
if (is_array($this->getSearchValue($query, $search, $field))) {
foreach ($this->getSearchValue($query, $search, $field) as $array) {
$query->orWhereRaw(
"UPPER(`$table`.`$field`) LIKE UPPER(?)",
['%' . htmlspecialchars($array->value) . '%']
);
}
return $query;
}
// Get models with relation's name/label.
if (Str::endsWith($field, '_id')) {
$query->orWhereHas(Str::remove('_id', $field), function ($queryRelation) use ($field, $search) {
if (Schema::hasColumn(Str::remove('_id', $field) . 's', 'name')) {
$queryRelation->where('name', 'like', '%' . $search . '%');
} elseif (Schema::hasColumn(Str::remove('_id', $field) . 's', 'label')) {
$queryRelation->where('label', 'like', '%' . $search . '%');
}
});
return $query;
}
$query->orWhereRaw(
"UPPER(`$table`.`$field`) LIKE UPPER(?)",
['%' . htmlspecialchars($this->getSearchValue($query, $search, $field)) . '%']
);
} //end foreach
return $query;
}
/**
* Get the search value (on string or Enum).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param string $field
* @return string|array
*/
protected function getSearchValue(Builder $query, string $search, string $field): string|array
{
$cast = $query->getModel()->getCasts()[$field] ?? null;
if (
count($query->getModel()->getCasts()) and // Check if the model has casts.
$cast and // Check if there is cast about specific field.
enum_exists($cast) // Check if the cast is an enum.
) {
/** @var \App\Traits\Enums $cast */
// @phpstan-ignore-next-line
$cases = collect($cast::toArray());
$enum = $cases->filter(function ($item) use ($search) {
return preg_match('/' . Str::lower($search) . '/', Str::lower($item->label));
});
return $enum->toArray();
} else {
return $search;
}
return $query;
}
/**
@ -216,32 +157,18 @@ class Controller extends BaseController
Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way");
}
if (
request()->sort_col &&
request()->sort_way &&
Schema::hasColumn($table, request()->sort_col) &&
(!isset($ignore) || (isset($ignore) && !in_array(request()->sort_col, $ignore)))
) {
$query = $query->orderBy("$table." . request()->sort_col, request()->sort_way);
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
if (
!isset($ignore) or
(isset($ignore) and !in_array(request()->sort_col, $ignore))
) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
}
} elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'ASC');
Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc');
}
return $query;
}
/**
* Customize pagination with cache or config (default).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param integer|null $pagination
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
protected function customPaginate(Builder $query, $pagination): LengthAwarePaginator
{
if ($pagination) {
Cache::put('pagination', $pagination);
};
$query = $query->paginate(
(Cache::get('pagination')) ? intval(Cache::get('pagination')) : config('pagination.default')
);
return $query;
}
}

View file

@ -0,0 +1,124 @@
<?php
namespace App\Http\Controllers\Credit;
use App\Enums\Credit\BalanceOperation;
use App\Http\Controllers\Controller;
use App\Http\Requests\Credit\AddCreditToAccountRequest;
use App\Models\Brand;
use App\Models\Credit\Account;
use App\Models\Credit\CreditLog;
use Illuminate\Database\Eloquent\Builder;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class AccountController extends Controller
{
use CrudController {
__construct as __CrudConstructor;
index as crudIndex;
}
/** @var Account */
public static $modelClass = Account::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* Index query middleware
*
* @param Builder $query
* @return Builder
* @throws \ErrorException If account is missing in url.
*/
public function indexQuery(Builder $query): Builder
{
/** @var \App\Models\User */
$user = auth()->user();
if (!$user->isAdmin()) {
$userBrands = $user->brands();
return $query->whereHas('brand', function ($q) use ($userBrands) {
$q->whereIn('brand_id', $userBrands->pluck('id'));
});
}
/** @var \App\Models\Brand|null $brand */
if (!($brand = request()->brand)) {
abort(404, 'Cannot get brand');
}
return $query->whereHas('brand', function ($q) use ($brand) {
$q->where('id', $brand->id);
});
}
/**
* 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();
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers\Credit;
use App\Http\Controllers\Controller;
use App\Models\Credit\CreditLog;
use Illuminate\Database\Eloquent\Builder;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class CreditLogController extends Controller
{
use CrudController {
__construct as __CrudConstructor;
index as crudIndex;
}
/** @var CreditLog */
public static $modelClass = CreditLog::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* Index query middleware
*
* @param Builder $query
* @return Builder
* @throws \ErrorException If account is missing in url.
*/
public function indexQuery(Builder $query): Builder
{
$query->orderBy('created_at', 'DESC');
/** @var \App\Models\User */
$user = auth()->user();
/** @var \App\Models\Credit\Account $account */
$account = request()->account;
return $query->whereHas('account', function ($q) use ($account) {
$q->where('id', '=', $account->id);
});
}
/**
* 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());
}
}

View file

@ -1,15 +1,15 @@
<?php
namespace App\Http\Controllers\Bo\Credit;
namespace App\Http\Controllers\Credit;
use App\Enums\Credit\EstimationStatusEnum;
use App\Enums\Tickets\TicketStatusEnum;
use App\Enums\Credit\EstimationStatus;
use App\Enums\TicketStatus;
use App\Events\NotifyAdmin;
use App\Events\NotifySubscriptions;
use App\Events\TicketStatusChanged;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Credit\AnswerEstimateRequest;
use App\Http\Requests\Bo\Credit\CreateEstimateRequest;
use App\Http\Requests\Credit\AnswerEstimateRequest;
use App\Http\Requests\Credit\CreateEstimateRequest;
use App\Models\Credit\Estimation;
use App\Models\Ticket;
use Illuminate\Support\Facades\DB;
@ -32,8 +32,8 @@ class EstimationController extends Controller
/**
* Create Ticket estimation
*
* @param \App\Http\Requests\Bo\Credit\CreateEstimateRequest $request
* @param \App\Models\Ticket $ticket
* @param \App\Http\Requests\Credit\CreateEstimateRequest $request
* @param \App\Models\Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function estimateSubmit(CreateEstimateRequest $request, Ticket $ticket)
@ -44,19 +44,19 @@ class EstimationController extends Controller
$estimate = new Estimation($request->validated());
$estimate->ticket()->associate($ticket);
$estimate->estimator()->associate($user);
$ticket->status = TicketStatusEnum::waitingValidation;
$ticket->status = TicketStatus::waitingValidation();
$ticket->saveQuietly();
if ($estimate->saveOrFail()) {
$message = sprintf(
'<b>%s</b> a modifié le statut en <b>%s</b>, Une estimation de %s jours à été ajoutée',
$user->name,
$ticket->status->label(),
$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('bo.ticket.show', $ticket)
return response()->redirectToRoute('ticket.show', $ticket)
->with('info', trans('L\'estimation à bien été ajouté'));
}
return \back();
@ -66,8 +66,8 @@ class EstimationController extends Controller
/**
* Answer ticket estimation
*
* @param \App\Http\Requests\Bo\Credit\AnswerEstimateRequest $request
* @param \App\Models\Ticket $ticket
* @param \App\Http\Requests\Credit\AnswerEstimateRequest $request
* @param \App\Models\Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function estimatePendingSubmit(AnswerEstimateRequest $request, Ticket $ticket)
@ -77,11 +77,11 @@ class EstimationController extends Controller
/** @var \App\Models\Credit\Estimation */
$estimation = $ticket->estimation;
if ($request->answer) {
$ticket->status = TicketStatusEnum::takenInAccount;
$estimation->status = EstimationStatusEnum::accepted;
$ticket->status = TicketStatus::takenInAccount();
$estimation->status = EstimationStatus::accepted();
} else {
$ticket->status = TicketStatusEnum::archived;
$estimation->status = EstimationStatusEnum::refused;
$ticket->status = TicketStatus::archived();
$estimation->status = EstimationStatus::refused();
}
$estimation->validator()->associate($user);
if ($estimation->saveOrFail() and $ticket->saveOrFail()) {
@ -89,34 +89,14 @@ class EstimationController extends Controller
'Une réponse à été fourni par %s pour l\'estimation du ticket n°%d : %s',
$user->name,
$ticket->id,
$estimation->status->label()
$estimation->status->label
);
event(new TicketStatusChanged($ticket, $ticket->status, $user));
event(new NotifySubscriptions($ticket, $message));
event(new NotifyAdmin($ticket, $message));
return response()->redirectToRoute('bo.ticket.show', $ticket)
return response()->redirectToRoute('ticket.show', $ticket)
->with('info', trans('Votre réponse à été enregistrée'));
}
return \back();
}
/**
* Handle submit and cancel an estimate on ticket
*
* @param \App\Models\Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function estimateCancelSubmit(Ticket $ticket)
{
$this->authorize('cancelEstimate', $ticket);
/** @var \App\Models\Credit\Estimation */
$estimation = $ticket->estimation;
$ticket->status = TicketStatusEnum::estimateTodo;
if ($estimation->deleteOrFail() and $ticket->saveOrFail()) {
return response()->redirectToRoute('bo.ticket.show', $ticket)
->with('info', trans('L\'estimation a été supprimée.'));
}
return \back();
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Dutyhour;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class DutyhourController extends Controller
{
use CrudController;
/** @var \App\Models\MacroSchedule\Marker */
public static $modelClass = Dutyhour::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
}

View file

@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers;
use App\Models\File;
use Illuminate\Database\Eloquent\Builder;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class FileController extends Controller
{
use CrudController {
index as crudIndex;
create as crudCreate;
}
/** @var File */
public static $modelClass = File::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* Index query middleware
*
* @param Builder $query
* @return Builder
*/
public function indexQuery(Builder $query): Builder
{
$parent = request()->site ?? request()->project;
if ($parent) {
$query->whereHasMorph('parent', [get_class($parent)], function ($q) use ($parent) {
return $q->whereId($parent->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('files.index', $view->getData());
}
/**
* Crud create view
*
* @param \Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function create(CrudIndexRequest $request)
{
/** @var \Illuminate\View\View */
$view = $this->crudCreate($request);
return view('files.create', $view->getData());
}
/**
* Before store model middleware
*
* @param \App\Models\File $file
* @return void
*/
public function storeModel(File &$file)
{
$file->parent()->associate(request()->site ?? request()->project);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers;
use App\Models\Dutyhour;
class HomeController extends Controller
{
/**
* Show Home page
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function home()
{
/**
* Get only duty hours for today or later.
* @var \Illuminate\Database\Eloquent\Builder $dutyhour
*/
$dutyhourModels = Dutyhour::where('date', '>', now()->modify('-1 day'))->orderBy('date', 'asc')->limit(10)->get();
return view('home', compact('dutyhourModels'));
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Information;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class InformationController extends Controller
{
use CrudController;
/** @var \App\Models\MacroSchedule\Marker */
public static $modelClass = Information::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* After store model middleware
*
* @return void
*/
public function storedModel()
{
cache()->put('infos', Information::all(), 3600);
}
/**
* After update model middleware
*
* @return void
*/
public function updatedModel()
{
cache()->put('infos', Information::all(), 3600);
}
/**
* After delete model middleware
*
* @return void
*/
public function deletedModel()
{
cache()->put('infos', Information::all(), 3600);
}
}

View file

@ -0,0 +1,282 @@
<?php
namespace App\Http\Controllers;
use App\Enums\Invoice\TaskType;
use App\Http\Requests\Invoice\InvoiceSetDateRequest;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Package;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
/**
* * Because update is denied when invoice is sent to the client
* ForceUpdate On signed date
* ForceUpdate On compta sent date
*/
class InvoiceController extends Controller
{
use CrudController {
index as crudIndex;
show as crudShow;
create as crudCreate;
edit as crudEdit;
}
/** @var \App\Models\Invoice */
public static $modelClass = Invoice::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* 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;
}
if ($request->search and ($models = $view->getData()['models'])->count() === 1) {
return \redirect()->route('invoices.edit', $models->first());
}
return view('invoices.index', $view->getData());
}
/**
* Index query middleware
*
* @param Builder $query
* @return Builder
* @throws \ErrorException If account is missing in url.
*/
public function indexQuery(Builder $query): Builder
{
$query->orderBy('created_at', 'DESC');
return $query->orWhere(function (Builder $q) {
$q->whereHas('site', function ($q) {
$q->where('name', 'LIKE', '%' . \filter_var(request()->search, \FILTER_SANITIZE_STRING) . '%');
})->orWhereHas('company', function ($q) {
$q->where('name', 'LIKE', '%' . \filter_var(request()->search, \FILTER_SANITIZE_STRING) . '%');
})->orWhereHas('companyInvoiced', function ($q) {
$q->where('name', 'LIKE', '%' . \filter_var(request()->search, \FILTER_SANITIZE_STRING) . '%');
});
});
}
/**
* Show model action
*
* @param \Illuminate\Http\Request $request
* @param mixed ...$params
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, ...$params)
{
/** @var \Illuminate\View\View|\Illuminate\Http\JsonResponse */
$view = call_user_func_array([$this, 'crudShow'], \array_merge([$request], $params));
if ($view instanceof \Illuminate\Http\JsonResponse) {
return $view;
}
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Package> */
$packages = Package::get()->keyBy('id')->all();
return view('invoices.show', \array_merge($view->getData(), ['packages' => $packages]));
}
/**
* Crud create view
*
* @param \Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function create(CrudIndexRequest $request)
{
/** @var \Illuminate\View\View */
$view = $this->crudCreate($request);
$companies = Company::get();
$packages = Package::get();
$taskRowModel = Invoice::getTaskStructure();
return view('invoices.create', \array_merge($view->getData(), [
'companies' => $companies,
'packages' => $packages,
'taskRowModel' => $taskRowModel,
]));
}
/**
* Before store model middleware
*
* @param \App\Models\Invoice $model
* @return void
*/
public function storeModel(Invoice &$model)
{
$this->validateTasks($model);
}
/**
* Before update model middleware
*
* @param \App\Models\Invoice $model
* @return void
* @throws \Illuminate\Validation\ValidationException If invoice is signed.
*/
public function updateModel(Invoice &$model)
{
if ($model->signed_date) {
throw ValidationException::withMessages([
'erreur' => trans('Il n\'est pas possible de mettre a jour un devis qui est signé')
]);
}
$this->validateTasks($model);
}
/**
* Edit model action
*
* @param mixed ...$params
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function edit(...$params)
{
/** @var \Illuminate\View\View */
$view = call_user_func_array([$this, 'crudEdit'], $params);
$companies = Company::get();
$packages = Package::get();
$taskRowModel = Invoice::getTaskStructure();
return view('invoices.edit', \array_merge($view->getData(), [
'companies' => $companies,
'packages' => $packages,
'taskRowModel' => $taskRowModel,
]));
}
/**
* Validate Tasks
*
* @param \App\Models\Invoice $invoice
* @return void
* @throws \Illuminate\Validation\ValidationException If tasks are not valid.
*/
private function validateTasks(Invoice $invoice): void
{
$tasks = \collect(\json_decode($invoice->tasks, true))
->map(function ($task) {
if (\is_array($task)) {
return \array_intersect_key($task, Invoice::getTaskStructure());
}
});
$invoice->tasks = Validator::make(['tasks' => $tasks->all()], [
'tasks' => 'required|array|min:1',
'tasks.*' => 'required|array',
'tasks.*.days' => 'required|numeric|min:0.125',
'tasks.*.amount' => 'required|numeric|min:0.125',
'tasks.*.desc' => 'required|string|min:10',
'tasks.*.type' => [
'required', new EnumIsValidRule(TaskType::class)
],
'tasks.*.package' => 'required_if:tasks.*.type,' .
TaskType::package()->value . '|nullable|exists:packages,id',
], [], [
'tasks' => trans('Tâches')
])->validated()['tasks'];
}
/**
* Download Invoice
*
* @param \App\Models\Invoice $invoice
* @return \Symfony\Component\HttpFoundation\Response
*/
public function download(Invoice $invoice)
{
$this->authorize($invoice, 'download');
return $invoice->generateInvoice()
->stream(Str::slug(trans('Devis :appname :date - ref :ref', [
'appname' => config('app.name'),
'date' => $invoice->created_at->isoFormat('YYYY-MM-DD'),
'ref' => $invoice->ref,
])) . '.pdf');
}
/**
* Set signed date
*
* @param \App\Http\Requests\Invoice\InvoiceSetDateRequest $request
* @param \App\Models\Invoice $invoice
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Validation\ValidationException If already signed.
*/
public function setSignedDate(InvoiceSetDateRequest $request, Invoice $invoice)
{
if ($invoice->signed_date) {
throw ValidationException::withMessages([
'erreur' => trans('La date de signature est déjà actée.')
]);
}
$invoice->signed_date = $request->date;
$invoice->saveOrFail();
return \back()->with('success', trans('La date de signature à bien été enregistrée'));
}
/**
* Set client signed date
*
* @param \App\Http\Requests\Invoice\InvoiceSetDateRequest $request
* @param \App\Models\Invoice $invoice
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Validation\ValidationException If already signed.
*/
public function setClientSentDate(InvoiceSetDateRequest $request, Invoice $invoice)
{
if ($invoice->sent_client_date) {
throw ValidationException::withMessages([
'erreur' => trans('La date de signature client est déjà actée.')
]);
}
$invoice->sent_client_date = $request->date;
$invoice->saveOrFail();
return \back()->with('success', trans('La date de signature client à bien été enregistrée'));
}
/**
* Set compta signed date
*
* @param \App\Http\Requests\Invoice\InvoiceSetDateRequest $request
* @param \App\Models\Invoice $invoice
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Validation\ValidationException If already signed.
*/
public function setComptaSentDate(InvoiceSetDateRequest $request, Invoice $invoice)
{
if ($invoice->sent_accounting_date) {
throw ValidationException::withMessages([
'erreur' => trans('La date de signature compta est déjà actée.')
]);
}
$invoice->sent_accounting_date = $request->date;
$invoice->saveOrFail();
return \back()->with('success', trans('La date de signature compta à bien été enregistrée'));
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use App\Models\Label;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class LabelController extends Controller
{
use CrudController;
/** @var Label */
public static $modelClass = Label::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
}

View file

@ -0,0 +1,77 @@
<?php
namespace App\Http\Controllers;
use App\Models\Log;
use Illuminate\Database\Eloquent\Builder;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class LogController extends Controller
{
use CrudController {
index as crudIndex;
}
/** @var Log */
public static $modelClass = Log::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* Index query middleware
*
* @param Builder $query
* @return Builder
*/
public function indexQuery(Builder $query): Builder
{
$parent = request()->site ?? request()->project;
if ($parent) {
$query->whereHasMorph('parent', [get_class($parent)], function ($q) use ($parent) {
return $q->whereId($parent->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)
{
// * Autosort
if (!$request->sort_col and !$request->sort_way) {
$request->merge([
'sort_col' => 'created_at',
'sort_way' => 'desc',
]);
}
/** @var \Illuminate\View\View */
$view = $this->crudIndex($request);
if ($request->ajax()) {
return $view;
}
return view('logs.index', $view->getData());
}
/**
* Before store model middleware
*
* @param \App\Models\Log $log
* @return void
*/
public function storeModel(Log &$log)
{
$log->parent()->associate(request()->site ?? request()->project);
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers;
use App\Models\MacroSchedule\Marker;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\View;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class MarkerController extends Controller
{
use CrudController;
/** @var \App\Models\MacroSchedule\Marker */
public static $modelClass = Marker::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* Crud index Hook
*
* @param Builder $query
* @return Builder
*/
public function indexQuery(Builder $query): Builder
{
$parent = request()->project;
if ($parent) {
$query->whereHas('project', function ($q) use ($parent) {
return $q->whereId($parent->id);
});
}
return $query;
}
/**
* Show create page middleware
*
* @return void
*/
public function createModel()
{
$this->shareLabelsToView();
}
/**
* Show edit model middleware
*
* @param Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function editModel(Model &$model)
{
$this->shareLabelsToView();
}
/**
* Before store model middleware
*
* @param \App\Models\MacroSchedule\Marker $marker
* @return void
*/
public function storeModel(Marker &$marker)
{
$marker->project()->associate(request()->project);
}
/**
* Share Marker labels to view
*
* @return void
*/
protected function shareLabelsToView()
{
View::share('markerLabels', Cache::remember('markers_labels', 20, function () {
return Marker::pluck('label');
}));
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Package;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class PackageController extends Controller
{
use CrudController;
/** @var \App\Models\MacroSchedule\Marker */
public static $modelClass = Package::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
}

View file

@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers;
use App\Models\Project;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class ProjectController extends Controller
{
use CrudController {
__construct as __CrudConstructor;
index as crudIndex;
show as crudShow;
}
/**
* ProjectController
*/
public function __construct()
{
parent::__construct();
$this->__CrudConstructor();
$this->authorizeResource(Project::class, 'project');
}
/** @var \App\Models\Project */
public static $modelClass = Project::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* 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('projects.index', $view->getData());
}
/**
* Crud show view
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Project $model
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function show(Request $request, Project $model)
{
/** @var \Illuminate\View\View */
$view = $this->crudshow($request, $model);
if ($view instanceof JsonResponse) {
return $view;
}
return view('projects.show', $view->getData());
}
}

View file

@ -0,0 +1,178 @@
<?php
namespace App\Http\Controllers;
use App\Enums\Role;
use App\Http\Requests\StoreScheduleRequest;
use App\Models\Schedule;
use App\Models\User;
use Illuminate\Http\Request;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
/**
* Alternate planner for Dev Team
*
* TODO: Refactor this controller.
*
* @author Olivier Legat <olivier.legat@ldc.fr>
*
*/
class ScheduleController extends Controller
{
/**
* ScheduleController
*/
public function __construct()
{
parent::__construct();
$this->middleware('is-admin');
}
public function index(Request $request)
{
$date = CarbonImmutable::now()->startOfWeek();
$nbweeks = config('schedules.shownWeeks');
if ($request->p) {
$date = CarbonImmutable::createFromFormat('d/m/Y', $request->p)->startOfWeek();
}
$dbusers = User::where('role', Role::developper())->get();
$users = [];
foreach ($dbusers as $user) {
$users[$user->id] = ['name' => $user->name];
}
$schedules = Schedule::whereHas('user', function(Builder $query) {
$query->where('deleted_at', NULL);
})
->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 = array('label' => null, 'value' => null);
if (!is_null($schedule->model_lie)) {
if (isset($schedule->relatedModel->name)) {
$id_lie = array('label' => $schedule->relatedModel->name, 'value' => $schedule->id_lie);
} else {
$id_lie = array('label' => 'Ticket ' . $schedule->id_lie, 'value' => $schedule->id_lie);
}
$model_lie = $schedule->model_lie;
} else {
$model_lie = array('label' => null, 'value' => null);
}
if ($prevUserId != $schedule->user_id) {
// nouveau 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,
// $schedule->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,
// $schedule->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,
// $schedule->id_lie,
'model_lie' => $model_lie,
);
} else {
// same date
// should never be here
}
}//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('schedule.index', compact('users', 'date', 'weekCells', 'arSchedules'));
}
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, 'value' => null);
if (!is_null($schedule->model_lie)) {
if (isset($schedule->relatedModel->name)) {
$id_lie = array('label' => $schedule->relatedModel->name, 'value' => $schedule->id_lie);
} else {
$id_lie = array('label' => 'Ticket ' . $schedule->id_lie, 'value' => $schedule->id_lie);
}
$model_lie = $schedule->model_lie;
} else {
$model_lie = array('label' => 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]);
}
public function eraseCellContent(Request $request)
{
$schedule = Schedule::findOrFail($request->id);
$schedule->delete();
return \response()->json(['status' => true]);
}
}

View file

@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers;
use App\Enums\ProjectStatus;
use App\Http\Requests\MacroSchedule\PostActivityRequest;
use App\Http\Requests\MacroSchedule\ShowMacroScheduleRequest;
use App\Models\MacroSchedule\Activity;
use App\Models\Project;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class ScheduleMacroController extends Controller
{
/**
* View Macro planning
*
* @param \App\Http\Requests\MacroSchedule\ShowMacroScheduleRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function show(ShowMacroScheduleRequest $request)
{
$numberOfWeeksToShow = \config('macroschedule.numberOfWeeksToShow');
$date = Carbon::now();
if ($request->date) {
$date = Carbon::createFromFormat('d/m/Y', $request->date);
}
/** @var \Illuminate\Support\Carbon $date The date from which to take activities */
$date->startOfWeek();
/** @var \Illuminate\Support\Collection $projects */
$projects = Project::where('status', '>', ProjectStatus::new())
->where('status', '<', ProjectStatus::finished())
->with(['activities' => function ($q) use ($date, $numberOfWeeksToShow) {
$q->whereDate('start', '>=', $date->toDateString())
->OrWhereDate('end', '<=', $date->clone()->addWeeks($numberOfWeeksToShow)->toDateString());
}])->with('markers')->orderBy('status')->orderBy('name')->get()->keyBy('id');
// * Key by date activities relation
$projects->map(function (Project $project) {
$project->setRelation('activities', $project->activities->keyBy('id'));
});
// * Map activities by datetime
$activities = collect();
$projects->pluck('activities')->map(function (Collection $projectActivities) use (&$activities) {
$projectActivities->map(function (Activity $activity) use (&$activities) {
$activities->push($activity);
});
});
// * Disponibility calculation
$loadCollection = collect();
/** @var array<\Illuminate\Support\Carbon> */
$period = $date->toPeriod($date->clone()->addWeeks($numberOfWeeksToShow), 12, 'hour')->toArray();
foreach ($period as $day) {
/** @var \Illuminate\Support\Collection */
$dayActivities = $activities->filter(function (Activity $activity) use ($day) {
return $day->betweenIncluded($activity->start, $activity->end);
});
/** @var integer total activités / hommes / demi journées */
$totalLoad = $dayActivities->sum('load');
$loadCollection->put($day->format('Y-m-d H:i:s'), $totalLoad / config('macroschedule.totalAvailableDevs'));
}
return view('macroplanning.view', compact(
'date',
'projects',
'date',
'numberOfWeeksToShow',
'loadCollection'
));
}
/**
* Save activity.
*
* @param PostActivityRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function postActivity(PostActivityRequest $request)
{
return DB::transaction(function () use ($request) {
$hasOverLap = false;
/** @var \App\Models\Project */
$project = Project::whereId($request->project_id)
->with('activities')->firstOrFail();
/** @var \App\Models\MacroSchedule\Activity */
$activity = Activity::whereId($request->id)->firstOrNew();
$activity->fill($request->validated());
$activity->project()->associate($project);
if (!$activity->saveOrFail()) {
response()->json('Une erreur est survenue lors de \'enregistrement');
}
// * Overload activities if necessary
$project->activities->map(function (Activity $periodActivity) use ($activity, &$hasOverLap) {
if (
$activity->id !== $periodActivity->id and
$activity->doesPeriodCoverActivity($periodActivity->start, $periodActivity->end)
) {
$hasOverLap = true;
$periodActivity->overlapUsingActivity($activity);
}
});
return response()->json([
'id' => $activity->id,
'hasOverlap' => $hasOverLap
]);
});
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers;
use App\Models\Site;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class SiteController extends Controller
{
use CrudController {
__construct as __CrudConstructor;
index as crudIndex;
}
/**
* SiteController
*/
public function __construct()
{
parent::__construct();
$this->__CrudConstructor();
$this->authorizeResource(Site::class, 'site');
}
/** @var Site */
public static $modelClass = Site::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* Site.index crud
*
* @param CrudIndexRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(CrudIndexRequest $request)
{
/** @var \Illuminate\View\View */
$view = $this->crudIndex($request);
if ($request->ajax()) {
return $view;
}
return view('sites.index', $view->getData());
}
}

View file

@ -0,0 +1,596 @@
<?php
namespace App\Http\Controllers;
use App\Enums\TicketStatus;
use App\Enums\TicketTypeEnum;
use App\Events\NotifySubscriptions;
use App\Events\TicketStatusChanged;
use App\Http\Requests\AssignTicketToUserRequest;
use App\Http\Requests\ChangeTicketStatus;
use App\Http\Requests\CreateMessage;
use App\Http\Requests\SortTickets;
use App\Http\Requests\StoreTicket;
use App\Http\Requests\UpdateMessage;
use App\Http\Requests\UpdateTicket;
use App\Lib\ToolboxHelper;
use App\Mail\NewTicket;
use App\Models\Message;
use App\Models\Site;
use App\Models\Ticket;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
class TicketController extends Controller
{
/**
* TicketController
*/
public function __construct()
{
parent::__construct();
$this->authorizeResource(Ticket::class, 'ticket');
}
/**
* Tickets List
*
* @param SortTickets $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(SortTickets $request)
{
// * Handle Ajax
if ($request->ajax()) {
$tickets = Ticket::with(['user', 'assignedUser']);
if ($request->s) {
$tickets = $tickets->where('text', 'LIKE', "%$request->s%")
->orWhere('id', $request->s)
->orWhereHas('user', function ($q) use ($request) {
$q->where('name', 'LIKE', "%$request->s%");
});
}
/** @var \Illuminate/Pagination/LengthAwarePaginator $tickets */
$tickets = $tickets->paginate();
return \response()->json($tickets->getCollection()->map(function (Ticket $ticket) {
return [
'label' => sprintf(
'Ticket %d (%s): %s',
$ticket->id,
$ticket->user ? $ticket->user->name : trans('Utilisateur supprimé'),
Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35)
),
'value' => $ticket->id
];
}));
}//end if
/** @var \App\Models\User */
$user = auth()->user();
$tickets = Ticket::with(['site', 'labels']);
if (!$user->isAdmin()) {
$tickets->whereHas('site', function ($q) use ($user) {
$q->whereHas('users', function ($q) use ($user) {
$q->whereId($user->id);
});
});
$tickets->where('is_internal', false);
} else {
$tickets->has('site');
}
$tickets->where(
'status',
($request->st == TicketStatus::archived()) ? '=' : '<>',
TicketStatus::archived()
);
$tickets = $this->searchTicket($tickets, $request);
$tickets = $this->filterTickets($tickets, $request);
$tickets = $tickets->paginate();
$sites = Site::select('id', 'name')->orderBy('name')->get();
$status = TicketStatus::toArray();
return view('ticket.index', compact('tickets', 'sites', 'status'));
}
/**
* Show Ticket
*
* @param Request $request
* @param Ticket $ticket
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function show(Request $request, Ticket $ticket)
{
if ($request->ajax()) {
return \response()->json([
'label' => sprintf(
'Ticket %d (%s): %s',
$ticket->id,
$ticket->user ? $ticket->user->email : __('Utilisateur supprimé'),
Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35)
),
'value' => $ticket->id
]);
}
/** @var \App\Models\User */
$user = auth()->user();
if (!$user->can('update', $ticket)) {
abort(403);
}
if ($ticket->is_internal && !$user->can('changeInternalStatus', $ticket)) {
abort(403);
}
$messages = $ticket->messages()->orderBy('created_at', 'DESC')->get();
$isSubscribed = (bool)$ticket->subscriptions()->where('user_id', $user->id)->count();
return view('ticket.show', compact('ticket', 'messages', 'isSubscribed'));
}
/**
* Show create Form
*
* @return \Illuminate\Http\RedirectResponse
*/
public function create()
{
/** @var \App\Models\User */
$user = auth()->user();
if ($user->isAdmin()) {
$sites = Site::orderBy('name')->get();
} else {
$sites = $user->sites;
}
if (!count($sites)) {
return redirect()->route('sites.create')
->with('warning', trans('Vous devez tout d\'abord créer un site pour y ajouter un ticket'));
}
return view('ticket.create', compact('sites'));
}
/**
* Create ticket
*
* @param StoreTicket $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreTicket $request)
{
try {
return DB::transaction(function () use ($request) {
/** @var \App\Models\Site */
$site = Site::findOrFail($request->site_id);
/** @var \App\Models\User */
$user = auth()->user();
$ticket = new Ticket($request->validated());
/** @var \Illuminate\Http\UploadedFile|null */
$fileTicket = $request->file_ticket;
/** @var \Illuminate\Http\UploadedFile|null */
$fileTicket2 = $request->file_ticket2;
if ($fileTicket) {
$ticket->file = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket);
}
if ($fileTicket2) {
$ticket->file2 = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket2);
}
$ticket->user()->associate(auth()->user());
$ticket->site()->associate($site);
$ticket->saveOrFail();
if ($request->labels) {
$ticket->labels()->sync($request->labels);
}
$user->subscriptions()->toggle($ticket->id);
if (config('mail.MAIL_ADMIN')) {
Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket));
} else {
throw new \ErrorException('Missing MAIL_ADMIN in .env');
}
return redirect()->route('ticket.show', $ticket)
->with('success', trans('Ticket enregistré !'));
});
} catch (\ErrorException $e) {
report($e);
Session::flash('error', trans('Une erreur est survenue'));
} //end try
return \back();
}
/**
* Show update form
*
* @param Ticket $ticket
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function edit(Ticket $ticket)
{
/** @var \App\Models\User */
$user = auth()->user();
if ($user->isAdmin()) {
$sites = Site::orderBy('name')->get();
} else {
$sites = $user->sites;
}
return view('ticket.edit', compact('ticket', 'sites'));
}
/**
* Update Model
*
* @param UpdateTicket $request
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateTicket $request, Ticket $ticket)
{
$ticket->fill($request->validated());
try {
return DB::transaction(function () use ($request, $ticket) {
/** @var \App\Models\Site */
$site = Site::findOrFail($request->site_id);
/** @var \Illuminate\Http\UploadedFile|null */
$fileTicket = $request->file_ticket;
/** @var \Illuminate\Http\UploadedFile|null */
$fileTicket2 = $request->file_ticket2;
if ($fileTicket) {
$ticket->file = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket);
}
if ($fileTicket2) {
$ticket->file2 = ToolboxHelper::storeImage($ticket->getTable(), $fileTicket2);
}
$ticket->labels()->sync($request->labels);
$ticket->site()->associate($site);
$ticket->saveOrFail();
return redirect()->route('ticket.edit', $ticket->id)
->with('success', 'Modifications enregistrées !');
});
} catch (\ErrorException $e) {
report($e);
Session::flash('error', trans('Une erreur est survenue'));
} //end try
return \back();
}
/**
* Change ticket Status
*
* @param ChangeTicketStatus $request
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function changeStatus(ChangeTicketStatus $request, Ticket $ticket)
{
$ticket->fill($request->validated());
$ticket->saveOrFail();
return back()
->with('warning', trans('Le status à été modifié'));
}
/**
* Add a new Message to the Ticket
*
* @param CreateMessage $request
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function createMessage(CreateMessage $request, Ticket $ticket)
{
try {
return DB::transaction(function () use ($request, $ticket) {
/** @var \Illuminate\Http\UploadedFile|null */
$fileMessage = $request->file_message;
$message = new Message($request->validated());
$message->user()->associate(auth()->user());
$message->ticket()->associate($ticket);
/** @var \App\Models\User */
$user = auth()->user();
if ($request->subscribe) {
if (!$user->subscriptions()->find($ticket->id)) {
$user->subscriptions()->attach($ticket->id);
}
} elseif ($user->subscriptions()->find($ticket->id)) {
$user->subscriptions()->detach($ticket->id);
}
$message->saveOrFail();
if ($fileMessage) {
$message->file = ToolboxHelper::storeImage($message->getTable(), $fileMessage);
$message->saveOrFail();
}
return back()
->with('success', trans('Votre message à été ajouté'));
});
} catch (\ErrorException $e) {
report($e);
Session::flash('error', trans('Une erreur est survenue'));
} //end try
return back();
}
/**
* Update Message
*
* @param UpdateMessage $request
* @param Ticket $ticket
* @param Message $message
* @return \Illuminate\Http\RedirectResponse
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function updateMessage(UpdateMessage $request, Ticket $ticket, Message $message)
{
$message->message = $request->message;
$message->saveOrFail();
return back()
->with('success', trans('Votre message à bien été modifié'));
}
/**
* Change ticket status to archived
*
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function setToArchive(Ticket $ticket)
{
$ticket->status = TicketStatus::archived();
$ticket->saveOrFail();
return redirect()->route('ticket.index')
->with('warning', trans('Le ticket est archivé'));
}
/**
* Subscribe to ticket
*
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function subscribe(Ticket $ticket)
{
/** @var \App\Models\User */
$user = auth()->user();
$user->subscriptions()->toggle($ticket->id);
if ($user->subscriptions()->where('ticket_id', $ticket->id)->exists()) {
return back()
->with('success', trans('Vous avez été inscrit'));
}
return back()->with('warning', trans('Vous avez été désinscrit'));
}
/**
* Reopen ticket
*
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function reopen(Ticket $ticket)
{
$message = trans(':ticketName à réouvert le ticket n°:ticketNumber', [
'ticketName' => $ticket->user->name,
'ticketNumber' => $ticket->id,
]);
/** @var \App\Enums\TicketStatus */
$oldStatus = $ticket->status;
$ticket->status = TicketStatus::inprogress();
$ticket->saveQuietly();
event(new TicketStatusChanged($ticket, $oldStatus, null, $message, false, false));
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
/** @var \App\Models\User */
$user = auth()->user();
$user->subscriptions()->toggle($ticket->id);
return back()->with('warning', trans('Votre ticket à été réouvert.'));
}
/**
* Assign ticket to user ticket
*
* @param AssignTicketToUserRequest $request
* @param Ticket $ticket
* @return \Illuminate\Http\RedirectResponse
*/
public function assignTo(AssignTicketToUserRequest $request, Ticket $ticket)
{
/** @var \App\Models\User */
$user = auth()->user();
/** @var \App\Models\User|null */
if ($assignedUser = User::whereId($request->assign)->first()) {
$message = trans(':admin a assigné le ticket n°:ticketNumber à :assignedAdmin', [
'admin' => $user->name,
'ticketNumber' => $ticket->id,
'assignedAdmin' => $assignedUser->name,
]);
$ticket->assignedUser()->associate($assignedUser);
} else {
$message = trans(
':admin a modifié l\'assignation du ticket n°:ticketNumber, il n\'est plus assigné à quelqu\'un.',
[
'admin' => $user->name,
'ticketNumber' => $ticket->id,
]
);
$ticket->assignedUser()->dissociate();
}
$ticket->saveQuietly();
/** @var \App\Enums\TicketStatus */
$oldStatus = $ticket->status;
event(new TicketStatusChanged($ticket, $oldStatus, null, $message, true, false));
event(new NotifySubscriptions($ticket, $message, collect([$ticket->user])));
/** @var \App\Models\User */
$user = auth()->user();
$user->subscriptions()->toggle($ticket->id);
if ($assignedUser) {
return back()->with('info', trans('Le ticket à été assigné à :assignedUser', [
'assignedUser' => $assignedUser->name
]));
}
return back()->with('info', trans('Le ticket n\'est plus assigné à personne'));
}
// * PRIVATES
/**
* Filter Tickets request constructor
*
* @param Builder $tickets
* @param Request $request
* @return Builder
*/
private function filterTickets(Builder $tickets, Request $request): Builder
{
if ($request->rst) {
Session::remove('tickets.ass');
Session::remove('tickets.st');
Session::remove('tickets.pr');
Session::remove('tickets.sort_col');
Session::remove('tickets.sort_way');
}
if ($request->assr) {
Session::remove('tickets.ass');
}
if ($request->prs) {
Session::remove('tickets.pr');
}
if ($request->sts) {
Session::remove('tickets.st');
}
if ($request->sort_col) {
Session::put('tickets.sort_col', $request->sort_col);
}
if ($request->sort_way) {
Session::put('tickets.sort_way', $request->sort_way);
}
if ($request->pr) {
Session::put('tickets.pr', $request->pr);
}
if ($request->st) {
Session::put('tickets.st', $request->st);
}
if ($request->ass) {
Session::put('tickets.ass', $request->ass);
}
Session::save();
if (Session::has('tickets.pr')) {
$tickets->where('site_id', Session::get('tickets.pr'));
}
if (Session::has('tickets.ass')) {
$tickets->whereHas('assignedUser', function ($q) {
$q->whereId(Session::get('tickets.ass'));
});
}
if (Session::has('tickets.st')) {
$tickets->where('status', Session::get('tickets.st'));
}
if (
Session::has('tickets.sort_col') and
Session::has('tickets.sort_way') and
in_array(Session::get('tickets.sort_col'), [
'id', 'site_id', 'created_at', 'created_by',
'type', 'status', 'assigned_to'
])
) {
$tickets->orderBy(
Session::get('tickets.sort_col'),
Session::get('tickets.sort_way')
);
} else {
$tickets->orderBy('id', 'DESC');
}
return $tickets;
}
/**
* Search ticket sql request constructor
*
* @param Builder $tickets
* @param Request $request
* @return Builder
*/
private function searchTicket(Builder $tickets, Request $request): Builder
{
if ($request->exists('search')) {
if (empty($request->search)) {
Session::remove('tickets.search');
} else {
Session::put('tickets.search', $request->search);
}
}
if (!Session::has('tickets.search')) {
return $tickets;
}
return $this->searchQuery($tickets, Session::get('tickets.search'), function ($q) {
$words = explode(' ', Session::get('tickets.search'));
if (\is_array($words)) {
foreach ($words as $word) {
if (!strlen($word)) {
continue;
}
$s = collect(TicketStatus::toArray())->filter(function (TicketStatus $t) use ($word) {
return Str::slug($t->label) === Str::slug($word);
})->map(function (TicketStatus $ticket) {
return $ticket->value;
});
$t = collect(TicketTypeEnum::toArray())->filter(function (TicketTypeEnum $s) use ($word) {
return Str::slug($s->label) === Str::slug($word);
})->map(function (TicketTypeEnum $ticket) {
return $ticket->value;
});
$q->orWhere('id', $word)
->orWhereIn('status', $s)
->orWhereIn('type', $t)
->orWhereHas('user', function ($q) use ($word) {
$q->where('name', 'LIKE', "%$word%");
});
} //end foreach
} //end if
}, 'text', 'file', 'file2');
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use App\Models\Ticket\TicketType;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class TicketTypeController extends Controller
{
use CrudController {
edit as crudEdit;
}
/** @var \App\Models\MacroSchedule\Marker */
public static $modelClass = TicketType::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* Edit model action
*
* @param mixed ...$params
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function edit(...$params)
{
/** @var \Illuminate\View\View */
$view = call_user_func_array([$this, 'crudEdit'], $params);
return view('ticket_types.edit', \array_merge($view->getData()));
}
}

View file

@ -0,0 +1,116 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Auth\ForgotPasswordController;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class UserController extends Controller
{
use CrudController {
__construct as __CrudConstructor;
}
/**
* UserController
*/
public function __construct()
{
parent::__construct();
$this->__CrudConstructor();
$this->authorizeResource(User::class, 'user');
}
/** @var User */
public static $modelClass = User::class;
/**
* Crud Layout
*
* @var string
*/
public static $layout = 'layout.app';
/**
* After store model middleware
*
* @param Model $model
* @return void
*/
public function storedModel(Model &$model)
{
/** @var \App\Models\User */
$model = $model;
$model->sendMailConfirm();
}
/**
* Password reset form
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function passwordReset()
{
return view('users.passwordReset');
}
/**
* Password reset submit
*
* @return \Illuminate\Http\RedirectResponse
*/
public function passwordResetSubmit()
{
/** @var \App\Models\User */
$user = auth()->user();
$request = new Request(['email' => $user->email]);
return app(ForgotPasswordController::class)->sendResetLinkEmail($request);
}
/**
* Show stats
*
* TODO: refator this using carbon.
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function stats()
{
/** @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('users.stats', ['stats' => array_reverse($stats)]);
}
/**
* Get february last day date of the year
*
* @param integer $year
* @return integer 28 or 29
*/
private function getLastDayFebruary(int $year): int
{
if ((is_int($year / 4) && !is_int($year / 100)) || is_int($year / 400)) {
return 29;
}
return 28;
}
}

Some files were not shown because too many files have changed in this diff Show more