Compare commits

...
Sign in to create a new pull request.

27 commits

Author SHA1 Message Date
Jérémy Munsch
c65549dde7
Invoice component fixed assign package on select 2022-08-30 15:06:27 +02:00
Jérémy Munsch
46167d600c
Invoice download add target blank 2022-08-30 15:06:00 +02:00
Vincent BRIDIER
7091fcf23c
Update gitignore 2022-08-30 14:54:53 +02:00
Vincent BRIDIER
c0fe0dd959
Fix Facture to Devis 2022-08-30 14:54:53 +02:00
Vincent BRIDIER
8b637f7b13
Fix SIO to SEO 2022-08-30 14:54:52 +02:00
Jérémy Munsch
1fd09c6bec
Fix invoice Search auto redirect 2022-08-30 14:54:02 +02:00
Jérémy Munsch
ce6fcd7f83
Fix Mpdf temp_dir 2022-08-30 14:49:48 +02:00
Jérémy Munsch
ab7b627cbc
Style lint sort alphabetical order 2022-08-30 13:46:53 +02:00
Jérémy Munsch
a33132e1d4
Invoice component fixed task parsing 2022-08-30 09:50:25 +02:00
Jérémy Munsch
40491b451f
Invoice Component fix free input step 2022-08-30 09:40:22 +02:00
Jérémy Munsch
5efb26e952
Schedulconponent Fixed dayslot parsing 2022-08-30 09:33:42 +02:00
Jérémy Munsch
8941c7ec81
Invoice controller fix typo 2022-08-30 09:27:18 +02:00
Jérémy Munsch
8a2700c554
add missing create 2022-08-30 09:26:47 +02:00
Jérémy Munsch
88e5379610
Invoice functionnality 2022-08-30 09:22:06 +02:00
Jérémy Munsch
86aa480c04
Stylelint 2022-08-30 09:22:05 +02:00
Jérémy Munsch
b5663d19ce
Add eslint for CI in package.json 2022-08-30 09:22:05 +02:00
Jérémy Munsch
847a543dab
Added Eslint + TS corrections 2022-08-30 09:22:04 +02:00
Jérémy Munsch
de29cda24f
Company controller 2022-08-30 09:22:04 +02:00
Jérémy Munsch
b1f0f0c885
Company + Invoice + Package + Enum TaskType Models and seeder 2022-08-30 09:22:04 +02:00
Jérémy Munsch
8a607030a4
Crud forfaits 2022-08-30 09:22:03 +02:00
Jérémy Munsch
0a7f27d221
Pass everything to Typescript + fix some js issues 2022-08-30 09:22:03 +02:00
Jérémy Munsch
45bc580c1e
Remove incident message 2022-08-30 09:20:34 +02:00
Jérémy Munsch
138c0ef2f2
require ldc/web-status-checker-laravel + remove obsolete command 2022-08-30 09:20:34 +02:00
Jérémy Munsch
f25b458c97
Bump create user command 2022-08-30 09:20:33 +02:00
Jérémy Munsch
9dc1aa728e
App service provider bump 2022-08-30 09:20:33 +02:00
Jérémy Munsch
18f104ae80
phpcs:pass 2022-08-30 09:20:32 +02:00
Jérémy Munsch
f4490e18d7
composer udate + npm update 2022-08-30 09:20:31 +02:00
164 changed files with 13806 additions and 6494 deletions

41
.eslintrc.json Normal file
View file

@ -0,0 +1,41 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:vue/vue3-strongly-recommended",
"@vue/eslint-config-typescript"
],
"overrides": [
{
"files": ["*.ts", "*.vue"],
"rules": {
"no-undef": "off"
}
},
{
"files": ["*.vue"],
"rules": {
"indent": "off"
}
}
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["vue", "@typescript-eslint"],
"ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
"rules": {
"indent": ["error", 4, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"vue/script-indent": ["error", 2, { "baseIndent": 0 }],
"vue/one-component-per-file": "off"
}
}

1
.gitignore vendored
View file

@ -18,3 +18,4 @@ yarn-error.log
package-lock.json
composer.lock
.idea/
auth.json

24
.stylelintrc.json Normal file
View file

@ -0,0 +1,24 @@
{
"extends": [
"stylelint-config-sass-guidelines",
"stylelint-config-idiomatic-order"
],
"ignoreFiles": [
"**/vendor/*.css",
"**/vendor/**/*.css",
"**/vendor/*.scss",
"**/vendor/**/*.scss",
"**/vendor/*.sass",
"**/vendor/**/*.sass",
"**/vendor/*.less",
"**/vendor/**/*.less"
],
"customSyntax": "postcss-scss",
"rules": {
"indentation": 4,
"string-quotes": "double",
"selector-max-id": null,
"color-no-invalid-hex": true,
"order/properties-alphabetical-order": null
}
}

View file

@ -1,12 +1,13 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#3371a9",
"activityBar.activeBorder": "#621e41",
"activityBar.activeBackground": "#3371a9",
"activityBar.background": "#3371a9",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#621e41",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#e7e7e799",
"sash.hoverBorder": "#3371a9",
"statusBar.background": "#275782",
"statusBar.foreground": "#e7e7e7",

View file

@ -6,6 +6,8 @@ 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;
class CreateUser extends Command
{
@ -30,19 +32,57 @@ class CreateUser extends Command
*/
public function handle()
{
$name = $this->ask('What is the name?');
$email = $this->ask('What is the email?');
$pwd = $this->secret('What is the password?');
$user = new User();
$user->name = $name;
$user->email = $email;
if ($this->confirm('Is the user an admin?')) {
$user->role = Role::admin();
} else {
$user->role = Role::client();
$email = null;
while (is_null($email)) {
try {
$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();
$email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
$user->password = Hash::make($pwd);
$user->save();
$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;
}
}
$password = null;
while (
($password = $this->secret(
(!\is_null($password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
};
$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,77 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
/*
* Command Name: LDC Web Status Checker
* Description: Envoi des infos Serveur/Laravel vers LDC Web
* Version: 1.1
* Author: Vincent BRIDIER
* Author URI: vincent.bridier@ldc.fr
*/
class LdcWebStatusChecker extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ldcweb:statuschecker';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Envoi des infos Serveur/Laravel vers LDC Web';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$data = [];
$data['uuid'] = config('ldcweb.project_uuid');
// Sitemap.xml ?
if (file_exists(public_path() . '/sitemap.xml')) {
$data['sitemap'] = 'true';
} else {
$data['sitemap'] = 'false';
}
// Robots.txt ?
if (file_exists(public_path() . '/robots.txt')) {
$data['robots'] = 'true';
} else {
$data['robots'] = 'false';
}
// Check logs
$data['logs'] = 0;
$patherror = storage_path('/logs');
$files = glob($patherror . '/*.log');
if (count($files) > 0) {
foreach ($files as $file) {
$data['logs'] = $data['logs'] + filesize($file);
}
}
$data['code']['version'] = app()->version();
// Send by cURL
$url = 'https://ldc-web.ldc.fr/api/status-checker';
$ch = curl_init($url);
$payload = ['token' => 'CE8S52Qq7stf5k8frF4T', 'datas' => json_encode($data)];
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
return 0;
}
}

View file

@ -12,9 +12,13 @@ use Kwaadpepper\Enum\BaseEnumRoutable;
* @method static self informations()
* @method static self feedback()
*/
class FileCategory extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
@ -27,6 +31,11 @@ class FileCategory extends BaseEnumRoutable
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [

View file

@ -8,9 +8,13 @@ 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 [
@ -19,6 +23,11 @@ class FileType extends BaseEnumRoutable
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [

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' => 460,
'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

@ -10,9 +10,13 @@ use Kwaadpepper\Enum\BaseEnumRoutable;
* @method static self high()
* @method static self overload()
*/
class LoadLevel extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
@ -23,6 +27,11 @@ class LoadLevel extends BaseEnumRoutable
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [

View file

@ -18,6 +18,11 @@ use Kwaadpepper\Enum\BaseEnumRoutable;
*/
class ActivityType extends BaseEnumRoutable
{
/**
* Enum labels
*
* @return array
*/
public static function labels()
{
return [
@ -30,6 +35,11 @@ class ActivityType extends BaseEnumRoutable
];
}
/**
* Enum colors
*
* @return array
*/
public static function colors(): array
{
return [

View file

@ -20,6 +20,11 @@ use Kwaadpepper\Enum\BaseEnumRoutable;
*/
class CellModel extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
@ -29,6 +34,11 @@ class CellModel extends BaseEnumRoutable
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [

View file

@ -17,6 +17,11 @@ use Kwaadpepper\Enum\BaseEnumRoutable;
*/
class CellType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
@ -29,6 +34,11 @@ class CellType extends BaseEnumRoutable
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [

View file

@ -18,6 +18,11 @@ use Kwaadpepper\Enum\BaseEnumRoutable;
*/
class TableRow extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
@ -32,6 +37,11 @@ class TableRow extends BaseEnumRoutable
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [

View file

@ -7,12 +7,14 @@ use App\Models\User;
class FileCreated extends Loggable
{
/** @var File */
/** @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)

View file

@ -11,14 +11,16 @@ abstract class Loggable
use Dispatchable;
use SerializesModels;
/** @var User */
/** @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)

View file

@ -13,7 +13,7 @@ class NotifyAdmin
use InteractsWithSockets;
use SerializesModels;
/** @var Ticket */
/** @var \App\Models\Ticket */
public $ticket;
/** @var string */
@ -22,6 +22,8 @@ class NotifyAdmin
/**
* Create a new event instance.
*
* @param \App\Models\Ticket $ticket
* @param string $corpsMaj
* @return void
*/
public function __construct(Ticket $ticket, string $corpsMaj)

View file

@ -14,7 +14,7 @@ class NotifySubscriptions
use InteractsWithSockets;
use SerializesModels;
/** @var Ticket */
/** @var \App\Models\Ticket */
public $ticket;
/** @var string */
@ -26,6 +26,9 @@ class NotifySubscriptions
/**
* Create a new event instance.
*
* @param \App\Models\Ticket $ticket
* @param string $corpsMaj
* @param \Illuminate\Support\Collection|null $ignoreUsers
* @return void
*/
public function __construct(Ticket $ticket, string $corpsMaj, Collection $ignoreUsers = null)

View file

@ -13,7 +13,7 @@ class NotifyTicketCreator
use InteractsWithSockets;
use SerializesModels;
/** @var Ticket */
/** @var \App\Models\Ticket */
public $ticket;
/** @var string */
@ -22,6 +22,8 @@ class NotifyTicketCreator
/**
* Create a new event instance.
*
* @param \App\Models\Ticket $ticket
* @param string $corpsMaj
* @return void
*/
public function __construct(Ticket $ticket, string $corpsMaj)

View file

@ -7,12 +7,14 @@ use App\Models\User;
class ProjectUpdated extends Loggable
{
/** @var Project */
/** @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)

View file

@ -7,12 +7,14 @@ use App\Models\User;
class SiteUpdated extends Loggable
{
/** @var Site */
/** @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)

View file

@ -2,6 +2,7 @@
namespace App\Events;
use App\Enums\TicketStatus;
use App\Models\Ticket;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
@ -14,12 +15,13 @@ class TicketStatusChanged
use InteractsWithSockets;
use SerializesModels;
/** @var Ticket */
/** @var \App\Models\Site */
public $ticket;
/** @var User */
/** @var \App\Models\User */
public $user;
/** @var \App\Enums\TicketStatus */
public $status;
/** @var string */
@ -28,9 +30,13 @@ class TicketStatusChanged
/**
* Create a new event instance.
*
* @param \App\Models\Ticket $ticket
* @param \App\Enums\TicketStatus $status
* @param \App\Models\User|null $user
* @param string|null $message
* @return void
*/
public function __construct(Ticket $ticket, $status, User $user = null, string $message = null)
public function __construct(Ticket $ticket, TicketStatus $status, User $user = null, string $message = null)
{
$this->ticket = $ticket;
$this->status = $status;

View file

@ -8,16 +8,6 @@ use Illuminate\Foundation\Auth\ConfirmsPasswords;
class ConfirmPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Confirm Password Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password confirmations and
| uses a simple trait to include the behavior. You're free to explore
| this trait and override any functions that require customization.
|
*/
use ConfirmsPasswords;
/**

View file

@ -7,18 +7,13 @@ use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
* ForgotPasswordController
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:web');

View file

@ -8,16 +8,6 @@ use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**

View file

@ -9,16 +9,6 @@ use Illuminate\Validation\Rules\Password;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**

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

@ -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;
use Symfony\Component\HttpFoundation\Response;
/**
* * 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): Response
{
$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

@ -16,8 +16,19 @@ class LogController extends Controller
/** @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;
@ -29,9 +40,15 @@ class LogController extends Controller
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
// * Autosort
if (!$request->sort_col and !$request->sort_way) {
$request->merge([
'sort_col' => 'created_at',
@ -47,6 +64,12 @@ class LogController extends Controller
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,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

@ -13,6 +13,8 @@ use Illuminate\Database\Eloquent\Builder;
/**
* Alternate planner for Dev Team
*
* TODO: Refactor this controller.
*
* @author Olivier Legat <olivier.legat@ldc.fr>
*
*/

View file

@ -402,10 +402,11 @@ class TicketController extends Controller
*/
public function reopen(Ticket $ticket)
{
$message = trans(':ticketName à réouvert le ticket n°:ticketNumber', [
$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();

View file

@ -6,7 +6,6 @@ use App\Http\Controllers\Auth\ForgotPasswordController;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class UserController extends Controller
@ -15,6 +14,9 @@ class UserController extends Controller
__construct as __CrudConstructor;
}
/**
* UserController
*/
public function __construct()
{
parent::__construct();
@ -25,8 +27,19 @@ class UserController extends Controller
/** @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 */
@ -34,11 +47,21 @@ class UserController extends Controller
$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 */
@ -47,12 +70,21 @@ class UserController extends Controller
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()
{
$user = Auth::user();
$stats = [];
/** @var \App\Models\User */
$user = auth()->user();
$stats = [];
$curYear = date('Y');
for ($i = 2021; $i <= date('Y'); $i++) {
for ($i = 2021; $i <= $curYear; $i++) {
$start = $i . '-03-01';
$end = $i + 1 . '-02-' . $this->getLastDayFebruary($i + 1);
@ -68,7 +100,13 @@ class UserController extends Controller
return view('users.stats', ['stats' => array_reverse($stats)]);
}
private function getLastDayFebruary($year)
/**
* 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;

View file

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests\Invoice;
use Illuminate\Foundation\Http\FormRequest;
class InvoiceSetDateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
/** @var \App\Models\User|null $user */
$user = auth()->user();
return auth()->check() && $user->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'date' => 'required|date'
];
}
}

View file

@ -15,7 +15,7 @@ class SetCellContentRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
$this->merge([
'model' => $this->model ?? null,
@ -30,7 +30,7 @@ class SetCellContentRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'id' => 'required|exists:week_plannings,id',
@ -52,7 +52,12 @@ class SetCellContentRequest extends FormRequest
];
}
public function attributes()
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'cellModel' => 'Type d\'élément lié',

View file

@ -70,9 +70,11 @@ class StoreScheduleRequest extends FormRequest
public function attributes(): array
{
return [
'activity' => trans('catégorie'),
'model_lie' => trans('type d\'élement lié'),
'id_lie' => trans('élement lié')
'user_id' => \trans('utilisateur'),
'activity' => \trans('type d\'activitée'),
'model_lie' => \trans('type d\'élément lié'),
'id_lie' => \trans('élément lié'),
'slot' => \trans('ligne de la journée'),
];
}
}

View file

@ -9,9 +9,9 @@ class UpdateMessage extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return bool
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
/** @var \App\Models\User|null $user */
$user = auth()->user();
@ -24,7 +24,7 @@ class UpdateMessage extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
/** @var \App\Models\User */
$user = auth()->user();

View file

@ -10,12 +10,13 @@ class UserLabeledResource extends ResourceCollection
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @param mixed $request
* @return array
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
public function toArray($request)
{
// phpcs:enable
return $this->collection->mapWithKeys(function (User $user, $key) {
return [
$key => [

View file

@ -28,6 +28,7 @@ class AutoCloseOldTickets
})->get();
/** @var \App\Models\Ticket $ticket */
foreach ($tickets as $ticket) {
/** @var \App\Enums\TicketStatus */
$oldStatus = $ticket->status;
$ticket->status = TicketStatus::archived();
$ticket->saveQuietly();

View file

@ -24,6 +24,8 @@ class SendTicketMentionMail implements ShouldQueue
/**
* Create a new job instance.
*
* @param \App\Models\User $user
* @param \App\Mail\TicketMention $mail
* @return void
*/
public function __construct(User $user, TicketMention $mail)

View file

@ -23,6 +23,7 @@ class SendUserCreatedEmail implements ShouldQueue
/**
* Create a new job instance.
*
* @param \App\Models\User $user
* @return void
*/
public function __construct(User $user)

View file

@ -0,0 +1,64 @@
<?php
namespace App\Lib;
use Illuminate\Container\Container;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
class CollectionHelper
{
/**
* Paginate a collection
*
* @param \Illuminate\Support\Collection $results
* @param integer $pageSize
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function paginate(Collection $results, int $pageSize): LengthAwarePaginator
{
$page = Paginator::resolveCurrentPage('page');
return self::paginateManually($results, $pageSize, $page);
}
/**
* Paginate a collection
*
* @param \Illuminate\Support\Collection $results
* @param integer $pageSize
* @param integer $page
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function paginateManually(Collection $results, int $pageSize, int $page = 1): LengthAwarePaginator
{
$total = $results->count();
return self::paginator($results->forPage($page, $pageSize), $total, $pageSize, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => 'page',
]);
}
/**
* Create a new length-aware paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param integer $total
* @param integer $perPage
* @param integer $currentPage
* @param array $options
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
protected static function paginator(Collection $items, int $total, int $perPage, int $currentPage, array $options)
{
return Container::getInstance()->makeWith(LengthAwarePaginator::class, [
'items' => $items,
'total' => $total,
'perPage' => $perPage,
'currentPage' => $currentPage,
'options' => $options
]);
}
}

View file

@ -10,7 +10,7 @@ class AddStatusMessages
/**
* Handle the event.
*
* @param TicketStatusChanged $event
* @param \App\Events\TicketStatusChanged $event
* @return void
*/
public function handle(TicketStatusChanged $event)

View file

@ -13,7 +13,7 @@ class HandleLoggable
/**
* Handle the event.
*
* @param Loggable $loggable
* @param \App\Events\Loggable $loggable
* @return void
*/
public function handle(Loggable $loggable)

View file

@ -11,7 +11,7 @@ class SendNotificationMailToAdmin
/**
* Handle the event.
*
* @param NotifyTicketCreator $event
* @param \App\Events\NotifyAdmin $event
* @return void
*/
public function handle(NotifyAdmin $event)

View file

@ -11,7 +11,7 @@ class SendNotificationMailToTicketCreator
/**
* Handle the event.
*
* @param NotifyTicketCreator $event
* @param \App\Events\NotifyTicketCreator $event
* @return void
*/
public function handle(NotifyTicketCreator $event)

View file

@ -13,7 +13,7 @@ class SendSubscriptionsNotificationsMail
/**
* Handle the event.
*
* @param NotifySubscriptions $event
* @param \App\Events\NotifySubscriptions $event
* @return void
*/
public function handle(NotifySubscriptions $event)

View file

@ -2,6 +2,7 @@
namespace App\Mail;
use App\Models\Ticket;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
@ -11,14 +12,16 @@ class NewTicket extends Mailable
use Queueable;
use SerializesModels;
/** @var \App\Models\Ticket */
private $ticket;
/**
* Create a new message instance.
*
* @param \App\Models\Ticket $ticket
* @return void
*/
public function __construct($ticket)
public function __construct(Ticket $ticket)
{
$this->ticket = $ticket;
}

View file

@ -13,7 +13,7 @@ class TicketMention extends Mailable
use Queueable;
use SerializesModels;
/** @var Ticket */
/** @var \App\Models\Ticket */
private $ticket;
/** @var string */
@ -22,6 +22,8 @@ class TicketMention extends Mailable
/**
* Create a new message instance.
*
* @param \App\Models\Ticket $ticket
* @param string $corpsMaj
* @return void
*/
public function __construct(Ticket $ticket, string $corpsMaj)

View file

@ -13,7 +13,7 @@ class TicketUpdate extends Mailable
use Queueable;
use SerializesModels;
/** @var Ticket */
/** @var \App\Models\Ticket */
private $ticket;
/** @var string */
@ -22,6 +22,8 @@ class TicketUpdate extends Mailable
/**
* Create a new message instance.
*
* @param \App\Models\Ticket $ticket
* @param string $corpsMaj
* @return void
*/
public function __construct(Ticket $ticket, string $corpsMaj)

View file

@ -7,11 +7,13 @@ use Illuminate\Mail\Mailable;
class UserCreatedMail extends Mailable
{
/** @var \App\Models\User */
protected $user;
/**
* Create a new message instance.
*
* @param \App\Models\User $user
* @return void
*/
public function __construct(User $user)

61
app/Models/Company.php Normal file
View file

@ -0,0 +1,61 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
use Kwaadpepper\CrudPolicies\Enums\CrudType;
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
class Company extends Model
{
use HasFactory;
use IsCrudModel;
/**
* Crud label column
*
* @var string
*/
protected $crudLabelColumn = 'name';
/**
* Crud value column
*
* @var string
*/
protected $crudValueColumn = 'id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name'
];
/**
* Company
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'name' => [
'label' => trans('Nom'),
'type' => CrudType::string(),
'nullable' => false,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::update(),
CrudAction::create()
]
],
];
parent::__construct($attributes);
}
}

View file

@ -18,18 +18,43 @@ class File extends Model
use HasFactory;
use IsCrudModel;
/**
* The attributes that should be cast to Enums.
*
* @var array
*/
protected $enumCasts = [
'category' => FileCategory::class,
'type' => FileType::class
];
/**
* Crud label column
*
* @var string
*/
protected $crudLabelColumn = 'name';
/**
* Crud value column
*
* @var string
*/
protected $crudValueColumn = 'id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['type', 'uri', 'file', 'name', 'category'];
public function __construct($attributes = [])
/**
* File
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'name' => [
@ -119,6 +144,11 @@ class File extends Model
parent::__construct($attributes);
}
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::created(function (File $file) {
@ -126,16 +156,33 @@ class File extends Model
});
}
// * RELATIONSHIPS
/**
* MorphTo parent object relation
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function parent()
{
return $this->morphTo();
}
/**
* MorphTo parent object site relation
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function parentSite()
{
return $this->morphTo('parent', 'site');
}
/**
* MorphTo parent object project relation
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function parentProject()
{
return $this->morphTo('parent', 'project');

390
app/Models/Invoice.php Normal file
View file

@ -0,0 +1,390 @@
<?php
namespace App\Models;
use App\Enums\Invoice\TaskType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
use Kwaadpepper\CrudPolicies\Enums\CrudType;
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
use Meneses\LaravelMpdf\LaravelMpdf;
class Invoice extends Model
{
use HasFactory;
use IsCrudModel;
/**
* Crud label column
*
* @var string
*/
protected $crudLabelColumn = 'ref';
/**
* Crud value column
*
* @var string
*/
protected $crudValueColumn = 'id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'ref', 'firstname', 'lastname',
'object', 'tasks', 'total', 'total_days',
'comment', 'show_days', 'signed_date',
'sent_client_date', 'sent_accounting_date',
'site_id', 'company_id', 'invoiced_to_id'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'tasks' => 'array',
'signed_date' => 'date',
'sent_client_date' => 'date',
'sent_accounting_date' => 'date',
];
/**
* Invoice
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'ref' => [
'label' => trans('Réference'),
'type' => CrudType::string(),
'nullable' => false,
'actions' => [
CrudAction::viewAny(),
CrudAction::view()
]
],
'firstname' => [
'label' => trans('Prénom'),
'type' => CrudType::string(),
'nullable' => false,
'actions' => [
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'lastname' => [
'label' => trans('Nom'),
'type' => CrudType::string(),
'nullable' => false,
'actions' => [
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'object' => [
'label' => trans('Object'),
'type' => CrudType::string(),
'nullable' => false,
'actions' => [
// * Truncated on view any
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'tasks' => [
'label' => trans('Tâches'),
'type' => CrudType::json(),
'nullable' => false,
'actions' => [
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'total' => [
'label' => trans('Montant total'),
'type' => CrudType::float(),
'nullable' => false,
'readonly' => true,
'actions' => [
CrudAction::view(),
]
],
'total_days' => [
'label' => trans('Montant total jours'),
'type' => CrudType::float(),
'nullable' => false,
'readonly' => true,
'actions' => [
CrudAction::view(),
]
],
'comment' => [
'label' => trans('Commentaire'),
'type' => CrudType::text(),
'nullable' => true,
'actions' => [
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'show_days' => [
'label' => trans('Afficher les jours'),
'type' => CrudType::boolean(),
'nullable' => false,
'actions' => [
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'signed_date' => [
'label' => trans('Date de signature'),
'type' => CrudType::datetime(),
'nullable' => true,
'readonly' => true,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
]
],
'sent_client_date' => [
'label' => trans('Date d\'envoi client'),
'type' => CrudType::datetime(),
'nullable' => true,
'readonly' => true,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
]
],
'sent_accounting_date' => [
'label' => trans('Date d\'envoi compta'),
'type' => CrudType::datetime(),
'nullable' => true,
'readonly' => true,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
]
],
'site_id' => [
'label' => trans('models.classes.sites'),
'type' => CrudType::belongsTo(),
'belongsTo' => Site::class,
'belongsToLabel' => 'site',
'nullable' => false,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'company_id' => [
'label' => trans('models.classes.companies'),
'type' => CrudType::belongsTo(),
'belongsTo' => Company::class,
'belongsToLabel' => 'companie',
'nullable' => false,
'actions' => [
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'invoiced_to_id' => [
'label' => trans('Entité à facturer'),
'type' => CrudType::belongsTo(),
'belongsTo' => Company::class,
'belongsToLabel' => 'entité',
'nullable' => false,
'actions' => [
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'created_at' => [
'label' => trans('Crée le'),
'type' => CrudType::datetime(),
'nullable' => false,
'actions' => [
CrudAction::view(),
]
],
'updated_at' => [
'label' => trans('Mis à jour'),
'type' => CrudType::datetime(),
'nullable' => false,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
]
],
];
parent::__construct($attributes);
}
/**
* The attributes that are mass assignable.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
$model->ref = \sprintf(
'%s_%s_%s',
Carbon::now()->isoFormat('YYYY'),
Str::padLeft((Invoice::whereYear('created_at', Carbon::now()->isoFormat('YYYY'))
->whereMonth('created_at', Carbon::now()->isoFormat('MM'))
->count() + 1) ?: 1, 4, '0'),
Str::substr(Str::upper(Str::slug(User::query()->inRandomOrder()->first()->name)), 0, 1)
);
$model->total_days = collect($model->tasks)
/** @var null|float $previous */
->reduce(function ($previous, array $task) {
return \floatval($task['days']) + \floatval($previous);
});
$model->total = collect($model->tasks)
/** @var null|float $previous */
->reduce(function ($previous, array $task) {
return \floatval($task['amount']) + \floatval($previous);
});
});
static::updating(function (self $model) {
$model->total_days = collect($model->tasks)
/** @var null|float $previous */
->reduce(function ($previous, array $task) {
return \floatval($task['days']) + \floatval($previous);
});
$model->total = collect($model->tasks)
/** @var null|float $previous */
->reduce(function ($previous, array $task) {
return \floatval($task['amount']) + \floatval($previous);
});
});
}
// * RELATIONSHIPS
/**
* BelongsTo Site
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function site()
{
return $this->BelongsTo(Site::class);
}
/**
* BelongsTo Company
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function company()
{
return $this->BelongsTo(Company::class);
}
/**
* BelongsTo Company
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function companyInvoiced()
{
return $this->BelongsTo(Company::class, 'invoiced_to_id');
}
// * METHODS
/**
* Get the tasks line structure skel
*
* @return array<string,string|float>
*/
public static function getTaskStructure(): array
{
return [
// * string
'desc' => '',
// * usigned float
'days' => 0,
// * Invoice\TaskType value
'type' => TaskType::web()->value,
// * usigned float
'amount' => 0,
// * package ID if task type package or null
'package' => null
];
}
/**
* Generate Invoice
*
* @return LaravelMpdf
*/
public function generateInvoice(): LaravelMpdf
{
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Package> */
$packages = Package::get()->keyBy('id')->all();
/** @var \Meneses\LaravelMpdf\LaravelMpdfWrapper */
$mpdf = app('mpdf.wrapper');
/** @var \Meneses\LaravelMpdf\LaravelMpdf */
$pdf = $mpdf->loadView(
'documents.invoice',
$this->toArray(),
[
'site' => $this->site,
'company' => $this->company,
'companyInvoiced' => $this->companyInvoiced,
'packages' => $packages,
],
[
'mode' => '',
'format' => 'A4',
'default_font_size' => '12',
'default_font' => 'sans-serif',
'margin_left' => 0,
'margin_right' => 0,
'margin_top' => 0,
'margin_bottom' => 0,
'margin_header' => 0,
'margin_footer' => 0,
'orientation' => 'P',
'title' => __('Devis Équipe Web :ref', ['ref' => $this->ref]),
'author' => 'Equipe Web',
'watermark' => '',
'show_watermark' => false,
'watermark_font' => 'sans-serif',
'display_mode' => 'fullpage',
'watermark_text_alpha' => 0.1,
'custom_font_dir' => '',
'custom_font_data' => [],
'auto_language_detection' => false,
'temp_dir' => ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir(),
'pdfa' => false,
'pdfaauto' => false,
]
);
return $pdf;
}
}

View file

@ -13,13 +13,33 @@ class Log extends Model
use HasFactory;
use IsCrudModel;
/**
* Crud label column
*
* @var string
*/
protected $crudLabelColumn = 'id';
/**
* Crud value column
*
* @var string
*/
protected $crudValueColumn = 'id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['entry'];
public function __construct($attributes = [])
/**
* Log
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'id' => [
@ -54,6 +74,11 @@ class Log extends Model
parent::__construct($attributes);
}
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (Log $log) {
@ -61,21 +86,43 @@ class Log extends Model
});
}
// * RELATIONSHIPS
/**
* BelongsTo User relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* MorphTo parent object relation
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function parent()
{
return $this->morphTo();
}
/**
* MorphTo parent object site relation
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function parentSite()
{
return $this->morphTo('parent', 'site');
}
/**
* MorphTo parent object project relation
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function parentProject()
{
return $this->morphTo('parent', 'project');

79
app/Models/Package.php Normal file
View file

@ -0,0 +1,79 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
use Kwaadpepper\CrudPolicies\Enums\CrudType;
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
class Package extends Model
{
use HasFactory;
use IsCrudModel;
/**
* Indicates if the model should be timestamped.
*
* @var boolean
*/
public $timestamps = false;
/**
* Crud label column
*
* @var string
*/
protected $crudLabelColumn = 'name';
/**
* Crud value column
*
* @var string
*/
protected $crudValueColumn = 'id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'amount'
];
/**
* Package
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'name' => [
'label' => trans('Nom'),
'type' => CrudType::string(),
'nullable' => false,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::update(),
CrudAction::create()
]
],
'amount' => [
'label' => trans('Montant (par jour)'),
'type' => CrudType::float(),
'nullable' => false,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::update(),
CrudAction::create()
]
],
];
parent::__construct($attributes);
}
}

View file

@ -14,6 +14,11 @@ class Schedule extends Model
use HasFactory;
use CastsEnums;
/**
* The attributes that should be cast to Enums.
*
* @var array
*/
protected $enumCasts = [
'activity' => ActivityType::class,
'model_lie' => CellModel::class
@ -28,6 +33,11 @@ class Schedule extends Model
'date' => 'date',
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'user_id',
'date',
@ -38,28 +48,34 @@ class Schedule extends Model
'model_lie'
];
// * RELATIONSHIPS
/**
* BelongsTo User relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
// relationship to linked objet
/**
* relationship to linked objet
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function relatedModel()
{
switch ($this->model_lie) {
case 'sites':
return $this->belongsTo(Site::class, 'id_lie');
break;
case 'projects':
return $this->belongsTo(Project::class, 'id_lie');
break;
case 'tickets':
return $this->belongsTo(Ticket::class, 'id_lie');
break;
default:
// no model
return new BelongsTo($this->newQuery(), $this, '', '', '');
break;
}
}
}

View file

@ -6,5 +6,10 @@ use Illuminate\Database\Eloquent\Relations\Pivot;
class SiteUser extends Pivot
{
/**
* Indicates if the model should be timestamped.
*
* @var boolean
*/
public $timestamps = false;
}

View file

@ -15,13 +15,14 @@ class AccountPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/

View file

@ -15,13 +15,14 @@ class BrandPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/

View file

@ -0,0 +1,38 @@
<?php
namespace App\Policies;
use App\Models\User;
use App\Policies\Rules\UserRoleRules as URR;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
class CompanyPolicy extends RootPolicy
{
use HandlesAuthorization;
/**
* Init policies on call
*
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'company' => [
'viewAny' => URR::make('userIsAdmin', $user),
'create' => URR::make('userIsAdmin', $user),
'update' => URR::make('userIsAdmin', $user),
'delete' => URR::make('userIsAdmin', $user)
]
];
}
}

View file

@ -15,10 +15,9 @@ class CreditLogPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{

View file

@ -15,13 +15,14 @@ class EstimationPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/

View file

@ -15,13 +15,14 @@ class FilePolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/

View file

@ -0,0 +1,47 @@
<?php
namespace App\Policies;
use App\Models\User;
use App\Policies\Rules\UserRoleRules as URR;
use App\Policies\Rules\InvoiceStaticRules as ISR;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
class InvoicePolicy extends RootPolicy
{
use HandlesAuthorization;
/**
* Init policies on call
*
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'invoice' => [
'viewAny' => URR::make('userIsAdmin', $user),
'view' => URR::make('userIsAdmin', $user),
'create' => URR::make('userIsAdmin', $user),
'update' => [
URR::make('userIsAdmin', $user),
ISR::make('invoiceHasNotBeenSentTotheClient', $user),
],
'delete' => URR::make('denyEveryOne'),
'download' => [
URR::make('userIsAdmin', $user),
ISR::make('invoiceHasTasks', $user),
]
]
];
}
}

View file

@ -15,13 +15,14 @@ class LabelPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/

View file

@ -17,10 +17,9 @@ class LogPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{

View file

@ -15,13 +15,14 @@ class MarkerPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/

View file

@ -0,0 +1,38 @@
<?php
namespace App\Policies;
use App\Models\User;
use App\Policies\Rules\UserRoleRules as URR;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
class PackagePolicy extends RootPolicy
{
use HandlesAuthorization;
/**
* Init policies on call
*
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'package' => [
'viewAny' => URR::make('userIsAdmin', $user),
'create' => URR::make('userIsAdmin', $user),
'update' => URR::make('userIsAdmin', $user),
'delete' => URR::make('userIsAdmin', $user)
]
];
}
}

View file

@ -15,13 +15,14 @@ class ProjectPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/

View file

@ -15,7 +15,7 @@ abstract class EstimationStaticRules extends StaticRules
/**
* Estimation is pending
*
* @param Estimation $model
* @param \App\Models\Credit\Estimation $model
* @return boolean
*/
protected static function estimateIsPending(Estimation $model): bool

View file

@ -0,0 +1,35 @@
<?php
namespace App\Policies\Rules;
use App\Models\Invoice;
use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
/**
* Invoice Policiy Static Rules
* ! Rules has to return boolean value
*/
abstract class InvoiceStaticRules extends StaticRules
{
/**
* Invoice has not been sent ot the client
*
* @param \App\Models\Invoice $model
* @return boolean
*/
protected static function invoiceHasNotBeenSentTotheClient(Invoice $model): bool
{
return \is_null($model->sent_client_date);
}
/**
* Invoice has some tasks
*
* @param \App\Models\Invoice $model
* @return boolean
*/
protected static function invoiceHasTasks(Invoice $model): bool
{
return (bool)\count($model->tasks);
}
}

View file

@ -15,8 +15,8 @@ abstract class LogStaticRules extends StaticRules
/**
* Log Is owned by user.
*
* @param User $user
* @param Log $log
* @param \App\Models\User $user
* @param \App\Models\Log $log
* @return boolean
*/
protected static function logIsOwnedByUser(User $user, Log $log): bool

View file

@ -12,6 +12,13 @@ use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
*/
abstract class SiteStaticRules extends StaticRules
{
/**
* Check if a user can access a ticket
*
* @param \App\Models\User $user
* @param \App\Models\Ticket $ticket
* @return boolean
*/
protected static function userSitesHasTicket(User $user, Ticket $ticket)
{
return $user->sites->contains($ticket->site_id);

View file

@ -15,7 +15,7 @@ abstract class TicketStaticRules extends StaticRules
/**
* Ticket is not in production
*
* @param Ticket $ticket
* @param \App\Models\Ticket $ticket
* @return boolean
*/
protected static function ticketIsNotInProduction(Ticket $ticket): bool
@ -26,7 +26,7 @@ abstract class TicketStaticRules extends StaticRules
/**
* Ticket is not archived
*
* @param Ticket $ticket
* @param \App\Models\Ticket $ticket
* @return boolean
*/
protected static function ticketIsNotArchived(Ticket $ticket): bool
@ -37,7 +37,7 @@ abstract class TicketStaticRules extends StaticRules
/**
* Ticket has brand which has at least one account
*
* @param Ticket $ticket
* @param \App\Models\Ticket $ticket
* @return boolean
*/
protected static function ticketsBrandHasAnAccount(Ticket $ticket): bool
@ -52,7 +52,7 @@ abstract class TicketStaticRules extends StaticRules
/**
* Ticket does already have an estimation
*
* @param Ticket $ticket
* @param \App\Models\Ticket $ticket
* @return boolean
*/
protected static function ticketDoesHaveEstimation(Ticket $ticket): bool
@ -63,7 +63,7 @@ abstract class TicketStaticRules extends StaticRules
/**
* Ticket does not already have an estimation
*
* @param Ticket $ticket
* @param \App\Models\Ticket $ticket
* @return boolean
*/
protected static function ticketDoesntHaveEstimation(Ticket $ticket): bool
@ -74,7 +74,7 @@ abstract class TicketStaticRules extends StaticRules
/**
* Ticket is not internal
*
* @param Ticket $ticket
* @param \App\Models\Ticket $ticket
* @return boolean
*/
protected static function ticketIsNotInternal(Ticket $ticket): bool

View file

@ -15,13 +15,14 @@ class SitePolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/

View file

@ -18,8 +18,8 @@ class TicketPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function setPolicies(User $user, Model $model = null)

View file

@ -15,13 +15,14 @@ class UserPolicy extends RootPolicy
/**
* Init policies on call
*
* @param User $user
* @param Model $model
* @param \App\Models\User $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
public function setPolicies(User $user, Model $model = null)
{
// phpcs:enable
/**
* This first rule to return true in a policy action authorises the action
*/

View file

@ -4,6 +4,7 @@ namespace App\Providers;
use App\Models\MacroSchedule\Marker;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\URL;
@ -12,15 +13,6 @@ use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
}
/**
* Bootstrap any application services.
*
@ -32,11 +24,31 @@ class AppServiceProvider extends ServiceProvider
URL::forceScheme('https');
if (config('app.env') === 'local') {
setlocale(LC_TIME, strtoupper(config('app.locale')) . "_" . config('app.locale') . '.utf8');
}
// * LOCAL FORCE eg: for Carbon
// * Remove numeric to prevent float seperator to breaking numeric transactions
\collect([\LC_TIME, \LC_MESSAGES, \LC_COLLATE, \LC_CTYPE, \LC_MONETARY])
->map(function ($contant) {
setlocale(
$contant,
\sprintf(
'%s_%s.UTF-8',
config('app.locale'),
\strtoupper(config('app.locale'))
)
);
});
Carbon::setLocale(config('app.locale'));
// * Force bootstrap paginator
Paginator::useBootstrap();
// * HA PROXY FIX
app()->singleton('clientIp', function () {
if (config('app.env') === 'local') {
return request()->getClientIp();
}
return request()->server('HTTP_X_FORWARDED_FOR') ?? request()->getClientIp();
});
if (!app()->runningInConsole()) {
$appInfos = Cache::remember('composer', 360, function () {

View file

@ -3,12 +3,15 @@
namespace App\Providers;
use App\Models\Brand;
use App\Models\Company;
use App\Models\Credit\Account;
use App\Models\Credit\CreditLog;
use App\Models\File;
use App\Models\Invoice;
use App\Models\Label;
use App\Models\Log;
use App\Models\MacroSchedule\Marker;
use App\Models\Package;
use App\Models\Project;
use App\Models\Site;
use App\Models\Ticket;
@ -87,6 +90,9 @@ class RouteServiceProvider extends ServiceProvider
Route::model('brand', Brand::class);
Route::model('label', Label::class);
Route::model('marker', Marker::class);
Route::model('package', Package::class);
Route::model('company', Company::class);
Route::model('invoice', Invoice::class);
Route::model('file', File::class);
Route::model('log', Log::class);
Route::model('user', User::class);

View file

@ -2,6 +2,7 @@
namespace App\Providers;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
@ -15,10 +16,8 @@ class TranslationServiceProvider extends ServiceProvider
*/
public function boot()
{
$translations = collect();
foreach ($this->collectLocalesStrings() as $locale) {
// suported locales
// * Suported locales
Cache::rememberForever(sprintf('translations.%s', $locale), function () use ($locale) {
$translations = [
'php' => $this->phpTranslations($locale),
@ -29,7 +28,12 @@ class TranslationServiceProvider extends ServiceProvider
}
}
private function collectLocalesStrings()
/**
* Gather provided langs checking from files
*
* @return \Illuminate\Support\Collection
*/
private function collectLocalesStrings(): Collection
{
return collect(File::allFiles(resource_path('lang/')))->flatMap(function ($file) {
if ($file->getRelativePath()) {
@ -38,23 +42,34 @@ class TranslationServiceProvider extends ServiceProvider
})->keys();
}
private function phpTranslations($locale)
/**
* Gather all php translations
*
* @param string $locale
* @return array
*/
private function phpTranslations(string $locale): array
{
$path = resource_path("lang/$locale");
return collect(File::allFiles($path))->flatMap(function ($file) use ($locale) {
$key = ($translation = $file->getBasename('.php'));
return [$key => trans($translation, [], $locale)];
});
$translation = $file->getBasename('.php');
return [$translation => trans($translation, [], $locale)];
})->all();
}
private function jsonTranslations($locale)
/**
* Gather json translations
*
* @param string $locale
* @return array
*/
private function jsonTranslations(string $locale): array
{
$path = resource_path("lang/$locale.json");
if (is_string($path) && is_readable($path)) {
return json_decode(file_get_contents($path), true);
return json_decode(file_get_contents($path), true) ?? [];
}
return [];

View file

@ -10,12 +10,13 @@
"license": "MIT",
"require": {
"php": "^7.3|^8.0",
"carlos-meneses/laravel-mpdf": "^2.1",
"doctrine/dbal": "^3.1",
"erusev/parsedown": "^1.7",
"fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"kwaadpepper/crud-policies": "^0.4.5",
"kwaadpepper/crud-policies": "^0.4.7",
"kwaadpepper/enum": "^2.1",
"kwaadpepper/exception-handler": "^0.0.9",
"laravel-lang/lang": "~8.0",
@ -23,6 +24,7 @@
"laravel/framework": "^8.12",
"laravel/tinker": "^2.5",
"laravel/ui": "^3.3",
"ldc/web-status-checker-laravel": "^1.0",
"mews/purifier": "^3.3"
},
"require-dev": {
@ -72,5 +74,11 @@
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
]
}
},
"repositories": [
{
"type": "composer",
"url": "https://gitlab.com/api/v4/group/16669037/-/packages/composer/packages.json"
}
]
}

1026
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,7 @@ return [
'default' => env('MAIL_MAILER', 'smtp'),
// Mail admin
// * Mail admin
'MAIL_ADMIN' => env('MAIL_ADMIN'),
/*

View file

@ -15,6 +15,7 @@ return [
* of your proxy (e.g. if using ELB or similar).
*
*/
'proxies' => '*',
/*
@ -42,6 +43,10 @@ return [
*
* @link https://symfony.com/doc/current/deployment/proxies.html
*/
'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_ALL,
'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_FOR |
Illuminate\Http\Request::HEADER_X_FORWARDED_HOST |
Illuminate\Http\Request::HEADER_X_FORWARDED_PORT |
Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO,
];

View file

@ -0,0 +1,28 @@
<?php
namespace Database\Factories;
use App\Models\Company;
use Illuminate\Database\Eloquent\Factories\Factory;
class CompanyFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Company::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->name,
];
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Database\Factories;
use App\Enums\Invoice\TaskType;
use App\Models\Invoice;
use App\Models\Package;
use Illuminate\Database\Eloquent\Factories\Factory;
class InvoiceFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Invoice::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
\mt_srand();
$lines = \mt_rand(2, 9);
$tasks = [];
$total = 0;
$totalDays = 0;
for ($i = 0; $i < $lines; $i++) {
$days = 0.125 * $this->faker->numberBetween(1, 4);
$type = \collect(TaskType::toArray())->random();
$amount = 0;
switch ($type) {
case TaskType::web():
$amount = $days * $type->price;
break;
case TaskType::seo():
$amount = $days * $type->price;
break;
case TaskType::package():
$amount = $days * (\optional(Package::query()->inRandomOrder()->first())->amount ??
$this->faker->randomFloat(null, 150, 650));
break;
case TaskType::package():
$amount = $days * $this->faker->randomFloat(null, 150, 650);
break;
}
$totalDays += $days;
$total += $amount;
/** @var \App\Enums\Invoice\TaskType */
$taskType = \collect(TaskType::toArray())->random();
$tasks[] = \array_merge(Invoice::getTaskStructure(), [
'desc' => $this->faker->words(9, true),
'days' => $days,
'type' => $taskType->value,
'amount' => $amount,
'package' => 1
]);
} //end for
return [
'firstname' => $this->faker->firstName,
'lastname' => $this->faker->lastName,
'object' => $this->faker->words(3, true),
// * constructed object
'tasks' => $tasks,
// * constructed object result
'total' => $total,
// * constructed object result
'total_days' => $totalDays,
'comment' => $this->faker->text(\rand(20, 40)),
'show_days' => $this->faker->boolean(75),
'signed_date' => $this->faker->boolean(25) ?
$this->faker->dateTimeBetween('-1 year', '+1 year') : null,
'sent_client_date' => $this->faker->boolean(25) ?
$this->faker->dateTimeBetween('-1 year', '+1 year') : null,
'sent_accounting_date' => $this->faker->boolean(25) ?
$this->faker->dateTimeBetween('-1 year', '+1 year') : null,
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Database\Factories;
use App\Models\Package;
use Illuminate\Database\Eloquent\Factories\Factory;
class PackageFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Package::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->name,
'amount' => $this->faker->unique()->randomFloat(2, 120, 850)
];
}
}

View file

@ -26,8 +26,8 @@ class UserFactory extends Factory
'name' => $this->faker->name,
'email' => $this->faker->unique()->safeEmail,
'email_verified_at' => now(),
// * password
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
// password
'remember_token' => Str::random(10),
];
}

View file

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePackagesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('packages', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedFloat('amount')->comment('The cost per day');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('packages');
}
}

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCompaniesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('companies', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('companies');
}
}

View file

@ -0,0 +1,73 @@
<?php
use App\Models\Company;
use App\Models\Site;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateInvoicesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->string('ref')
->comment(sprintf(
'%s. %s. %s. %s.',
'The invoice reference built as YYYY_MM_INCREMENT_USER',
'INCREMENT is the N invoice of the month',
'USER is the first two letters of the user who last edited the invoice',
'This field has to generated only on create'
));
$table->string('firstname')->comment('The person to which the invoice is addressed.');
$table->string('lastname')->comment('The person to which the invoice is addressed.');
$table->string('object')->comment('The invoice object.');
$table->text('tasks')
->comment(
// phpcs:ignore Generic.Files.LineLength.TooLong
'A JSON object, each line has to be contructed using these fields description, days, type Invoice\TaskType, amount'
);
$table->unsignedFloat('total')->comment('The total calculated using all tasks');
$table->unsignedFloat('total_days')->comment('The total days calculated using all tasks');
$table->text('comment')->nullable()
->comment('Any comment joined to the invoice');
$table->boolean('show_days')->comment('Show days on the printed invoice');
$table->timestamp('signed_date')->nullable()
->comment('The date the invoice was signed');
$table->timestamp('sent_client_date')->nullable()
->comment('The date the invoice was send to the client');
$table->timestamp('sent_accounting_date')->nullable()
->comment('The date the invoice was send to the client');
$table->foreignIdFor(Site::class)->constrained();
$table->foreignIdFor(Company::class)->constrained();
$table->foreignId('invoiced_to_id')
->constrained((new Company())->getTable());
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('invoices');
}
}

View file

@ -13,6 +13,5 @@ class DatabaseSeeder extends Seeder
*/
public function run()
{
// \App\Models\User::factory(10)->create();
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Database\Seeders;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Package;
use App\Models\Site;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class InvoicingSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
/** Reset all invoices */
DB::table((new Invoice())->getTable())->truncate();
/** Reset all packages (forfaits) */
Package::all()->map(function (Package $model) {
$model->deleteOrFail();
});
Package::factory(6)->create();
/** Reset all companies */
Company::all()->map(function (Company $model) {
$model->deleteOrFail();
});
Company::factory(6)->create();
Invoice::factory(45)->make()->each(function (Invoice $model) {
$model->ref = \sprintf(
'%s_%s_%s',
Carbon::now()->isoFormat('YYYY-MM'),
Str::padLeft((Invoice::whereYear('created_at', Carbon::now()->isoFormat('YYYY'))
->whereMonth('created_at', Carbon::now()->isoFormat('MM'))
->count() + 1) ?: 1, 4, '0'),
Str::substr(Str::upper(Str::slug(User::query()->inRandomOrder()->first()->name)), 0, 1)
);
$model->site()->associate(Site::query()->inRandomOrder()->first());
$model->company()->associate(Company::query()->inRandomOrder()->first());
$model->companyInvoiced()->associate(Company::query()->inRandomOrder()->first());
$model->saveQuietly();
});
}
}

View file

@ -1,5 +1,3 @@
{
"include": [
"./resources/js/**/*"
]
}
{
"include": ["./resources/ts/**/*"]
}

8820
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,36 +8,57 @@
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"production": "npm run clear-manifest && mix --production",
"prod": "npm run production"
"prod": "npm run production",
"eslint": "node ./node_modules/.bin/eslint -c .eslintrc.json ./resources",
"eslint-ci": "node ./node_modules/.bin/eslint -c .eslintrc.json ./resources -f junit",
"stylelint": "npx stylelint '**/*.scss'",
"stylelint-ci": "npx stylelint '**/*.scss' -f json"
},
"devDependencies": {
"@babel/plugin-transform-async-to-generator": "^7.16.8",
"@fortawesome/fontawesome-free": "^6.1.1",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-brands-svg-icons": "^6.1.2",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/vue-fontawesome": "^3.0.1",
"@popperjs/core": "^2.11.5",
"@vue/compiler-sfc": "^3.2.37",
"autoprefixer": "10.4.5",
"@types/bootstrap": "^5.1.12",
"@types/toastify-js": "^1.11.1",
"@types/vue-select": "^3.16.0",
"@types/webpack-env": "^1.17.0",
"@typescript-eslint/eslint-plugin": "^5.35.1",
"@typescript-eslint/parser": "^5.35.1",
"@vue/eslint-config-typescript": "^11.0.0",
"axios": "^0.21.1",
"bootstrap": "^5.1.3",
"clean-webpack-plugin": "^4.0.0",
"cross-env": "^7.0",
"css-loader": "^6.7.1",
"del-cli": "^4.0.1",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.4.0",
"eslint-webpack-plugin": "^3.2.0",
"glightbox": "^3.2.0",
"laravel-mix": "^6.0.16",
"laravel-mix": "^6.0.6",
"laravel-mix-clean": "^0.1.0",
"laravel-mix-eslint": "^0.2.0",
"laravel-mix-polyfill": "^3.0.1",
"laravel-mix-stylelint": "^1.0.0",
"lodash": "^4.17.19",
"moment": "^2.29.3",
"resolve-url-loader": "^3.1.0",
"sass": "^1.15.2",
"sass-loader": "^8.0.2",
"prettier": "^2.7.1",
"resolve-url-loader": "^5.0.0",
"sass": "^1.52.1",
"sass-loader": "^12.6.0",
"stylelint": "^14.11.0",
"stylelint-config-idiomatic-order": "^8.1.0",
"stylelint-config-sass-guidelines": "^9.0.1",
"stylelint-webpack-plugin": "^3.3.0",
"sweetalert2": "^11.4.16",
"toastify-js": "^1.11.2",
"vue": "^2.6.14",
"vue-loader": "^15.9.7",
"vue-select": "^3.13.0",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.6.12"
"ts-loader": "^9.3.0",
"typescript": "^4.7.4",
"vue": "^3.2.36",
"vue-loader": "^16.8.3",
"vue-select": "^4.0.0-beta.3"
}
}

View file

@ -1,14 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="LDC Laravel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
<ruleset name="LDC Laravel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
<description>Created with the PHP Coding Standard Generator. http://edorian.github.com/php-coding-standard-generator/</description>
<arg name="colors" />
<arg name="extensions" value="php" />
<file>app/</file>
<file>config/</file>
<file>database/factories</file>
<file>database/seeders</file>
<file>routes/</file>
<file>tests/</file>
<rule ref="PSR1" />
<rule ref="PSR12" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

6
resources/js/app.js vendored
View file

@ -1,6 +0,0 @@
require('./bootstrap')
require('./session_watch')
require('./vue')
require('./files_add')
require('./label_colorize')
require('./confirm')

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