Add feature tests, unit tests, and configuration for Laravel application
- Created ExampleTest and RoutesTest for basic application response and route testing. - Implemented IsolatedDatabaseTestCase for isolated database testing. - Added unit tests for various models and services including Contract, CPE, and LeadDetail. - Developed request validation tests for CreateClientRequest, UpdateClientRequest, and ContractRequest. - Introduced Vite configuration for asset management in the Laravel application.
This commit is contained in:
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[docker-compose.yml]
|
||||||
|
indent_size = 4
|
||||||
66
.env.example
Normal file
66
.env.example
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_URL=http://localhost
|
||||||
|
API_URL=http://localhost/api
|
||||||
|
|
||||||
|
APP_LOCALE=en
|
||||||
|
APP_FALLBACK_LOCALE=en
|
||||||
|
APP_FAKER_LOCALE=en_US
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
# APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
PHP_CLI_SERVER_WORKERS=4
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
DB_CONNECTION=mariadb
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=volcerto
|
||||||
|
DB_USERNAME=root
|
||||||
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
SESSION_DRIVER=database
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=false
|
||||||
|
SESSION_PATH=/
|
||||||
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=database
|
||||||
|
|
||||||
|
CACHE_STORE=database
|
||||||
|
# CACHE_PREFIX=
|
||||||
|
|
||||||
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=log
|
||||||
|
MAIL_SCHEME=null
|
||||||
|
MAIL_HOST=127.0.0.1
|
||||||
|
MAIL_PORT=2525
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_FROM_ADDRESS="hello@example.com"
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID=
|
||||||
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
AWS_DEFAULT_REGION=us-east-1
|
||||||
|
AWS_BUCKET=
|
||||||
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
*.blade.php diff=html
|
||||||
|
*.css diff=css
|
||||||
|
*.html diff=html
|
||||||
|
*.md diff=markdown
|
||||||
|
*.php diff=php
|
||||||
|
|
||||||
|
/.github export-ignore
|
||||||
|
CHANGELOG.md export-ignore
|
||||||
|
.styleci.yml export-ignore
|
||||||
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/.phpunit.cache
|
||||||
|
/node_modules
|
||||||
|
/public/build
|
||||||
|
/public/hot
|
||||||
|
/public/storage
|
||||||
|
/storage/*.key
|
||||||
|
/storage/pail
|
||||||
|
/vendor
|
||||||
|
.env
|
||||||
|
.env.backup
|
||||||
|
.env.production
|
||||||
|
.env.testing
|
||||||
|
.phpactor.json
|
||||||
|
.phpunit.result.cache
|
||||||
|
Homestead.json
|
||||||
|
Homestead.yaml
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
/auth.json
|
||||||
|
/.fleet
|
||||||
|
/.idea
|
||||||
|
/.nova
|
||||||
|
/.vscode
|
||||||
|
/.zed
|
||||||
|
lang/vendor/backup/
|
||||||
21
SECURITY.md
Normal file
21
SECURITY.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Use this section to tell people about which versions of your project are
|
||||||
|
currently being supported with security updates.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 5.1.x | :white_check_mark: |
|
||||||
|
| 5.0.x | :x: |
|
||||||
|
| 4.0.x | :white_check_mark: |
|
||||||
|
| < 4.0 | :x: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Use this section to tell people how to report a vulnerability.
|
||||||
|
|
||||||
|
Tell them where to go, how often they can expect to get an update on a
|
||||||
|
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||||
|
declined, etc.
|
||||||
111
app/Console/Commands/CleanMinioBackups.php
Normal file
111
app/Console/Commands/CleanMinioBackups.php
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class CleanMinioBackups extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'backup:clean-minio';
|
||||||
|
protected $description = 'Keeps only the last 5 backups in MinIO, safely removing older files';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$disk = Storage::disk(env('FILESYSTEM_DISK', 's3'));
|
||||||
|
$folder = 'laravel-backup';
|
||||||
|
$keep = 5;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->info('Connecting to MinIO bucket...');
|
||||||
|
|
||||||
|
$allFiles = $disk->allFiles($folder);
|
||||||
|
|
||||||
|
if (empty($allFiles)) {
|
||||||
|
$this->info('No backup files found in the directory.');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Found ' . count($allFiles) . ' files. Processing...');
|
||||||
|
|
||||||
|
$zipFiles = collect($allFiles)
|
||||||
|
->filter(fn($file) => str_ends_with($file, '.zip'))
|
||||||
|
->values();
|
||||||
|
|
||||||
|
if ($zipFiles->isEmpty()) {
|
||||||
|
$this->info('No ZIP backup files found.');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Found ' . $zipFiles->count() . ' ZIP files.');
|
||||||
|
|
||||||
|
$backups = $zipFiles->map(function ($file) use ($disk) {
|
||||||
|
try {
|
||||||
|
$lastModified = (int) $disk->lastModified($file);
|
||||||
|
return [
|
||||||
|
'file' => $file,
|
||||||
|
'lastModified' => $lastModified,
|
||||||
|
'date' => date('Y-m-d H:i:s', $lastModified),
|
||||||
|
'estimated' => false
|
||||||
|
];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->warn("Cannot read metadata for {$file}, using filename for sorting");
|
||||||
|
|
||||||
|
preg_match('/(\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2})/', $file, $matches);
|
||||||
|
|
||||||
|
$timestamp = isset($matches[1])
|
||||||
|
? \DateTime::createFromFormat('Y-m-d-H-i-s', $matches[1])->getTimestamp()
|
||||||
|
: time();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'file' => $file,
|
||||||
|
'lastModified' => $timestamp,
|
||||||
|
'date' => date('Y-m-d H:i:s', $timestamp),
|
||||||
|
'estimated' => true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$backups = $backups->sortByDesc('lastModified')->values();
|
||||||
|
|
||||||
|
$totalBackups = $backups->count();
|
||||||
|
$toDelete = $backups->slice($keep);
|
||||||
|
|
||||||
|
$this->info("Valid backups found: {$totalBackups}");
|
||||||
|
if ($toDelete->isEmpty()) {
|
||||||
|
$this->info("Only {$totalBackups} backup(s) found (less than {$keep}), nothing to delete.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("Keeping the latest {$keep}, deleting {$toDelete->count()} old backups.");
|
||||||
|
|
||||||
|
$this->info("Backups scheduled for deletion:");
|
||||||
|
foreach ($toDelete as $item) {
|
||||||
|
$this->info(" - {$item['file']} ({$item['date']})" . ($item['estimated'] ? ' [estimated]' : ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
$deletedCount = 0;
|
||||||
|
foreach ($toDelete as $item) {
|
||||||
|
try {
|
||||||
|
$disk->delete($item['file']);
|
||||||
|
$this->info("✓ Deleted: {$item['file']}" . ($item['estimated'] ? ' [estimated]' : ''));
|
||||||
|
$deletedCount++;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error("✗ Failed to delete {$item['file']}: {$e->getMessage()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("Cleanup finished. Deleted {$deletedCount} files. Kept the last {$keep} backups.");
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error("Failed to access backup directory: {$e->getMessage()}");
|
||||||
|
$this->error('Debug info:');
|
||||||
|
$this->error('Bucket: ' . env('AWS_BUCKET'));
|
||||||
|
$this->error('Endpoint: ' . env('AWS_ENDPOINT'));
|
||||||
|
$this->error('Key: ' . substr(env('AWS_ACCESS_KEY_ID', ''), 0, 5) . '...');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Console/Commands/SendTestEmail.php
Normal file
31
app/Console/Commands/SendTestEmail.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Http\Controllers\EmailTestController;
|
||||||
|
|
||||||
|
class SendTestEmail extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Nome do comando Artisan
|
||||||
|
*/
|
||||||
|
protected $signature = 'email:send-test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descrição do comando
|
||||||
|
*/
|
||||||
|
protected $description = 'Envia o e-mail de teste automaticamente';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executa o comando
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
(new EmailTestController)->sendTestEmail();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->error('Erro ao enviar e-mail: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
app/Exports/ContractsExport.php
Normal file
52
app/Exports/ContractsExport.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exports;
|
||||||
|
|
||||||
|
use App\Models\Contract;
|
||||||
|
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||||
|
|
||||||
|
class ContractsExport implements FromCollection, WithHeadings
|
||||||
|
{
|
||||||
|
public function collection()
|
||||||
|
{
|
||||||
|
return Contract::with(['client', 'seller'])->get()->map(function ($contract) {
|
||||||
|
return [
|
||||||
|
'Client' => $contract->client->name ?? '',
|
||||||
|
'Seller' => $contract->seller->name ?? '',
|
||||||
|
'Code' => $contract->code,
|
||||||
|
'Supply Type' => strtoupper($contract->supply_type),
|
||||||
|
'Power/CUI' => $contract->supply_type === 'cpe'
|
||||||
|
? ($contract->powerTier->name ?? '')
|
||||||
|
: ($contract->cuiTier->name ?? ''),
|
||||||
|
'Consumption' => $contract->annual_consumption ?? '',
|
||||||
|
'Contract Type'=> $contract->contractType->description ?? '',
|
||||||
|
'PVP' => $contract->contract_value,
|
||||||
|
'Commission' => $contract->commission_value,
|
||||||
|
'Status' => $contract->status->description ?? '',
|
||||||
|
'Activation' => optional($contract->activation_date)->format('d/m/Y'),
|
||||||
|
'Validity' => optional($contract->validity)->format('d/m/Y'),
|
||||||
|
'Contract End' => optional($contract->contract_end_date)->format('d/m/Y'),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function headings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Cliente',
|
||||||
|
'Vendedor',
|
||||||
|
'Código',
|
||||||
|
'Tipo Fornecimento',
|
||||||
|
'Potência / Escalão',
|
||||||
|
'Consumo Anual',
|
||||||
|
'Tipo Contrato',
|
||||||
|
'PVP',
|
||||||
|
'Comissão',
|
||||||
|
'Estado',
|
||||||
|
'Data Ativação',
|
||||||
|
'Validade',
|
||||||
|
'Fim de Contrato',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/Http/Controllers/AdminController.php
Normal file
17
app/Http/Controllers/AdminController.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Services\AdminService;
|
||||||
|
|
||||||
|
class AdminController extends Controller
|
||||||
|
{
|
||||||
|
public function main(AdminService $admin) {
|
||||||
|
return view('web.admin.system', $admin->info());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function phpInfo(AdminService $admin)
|
||||||
|
{
|
||||||
|
return response($admin->phpInfo())->header('Content-Type', 'text/html');
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/Http/Controllers/AnalyticalController.php
Normal file
18
app/Http/Controllers/AnalyticalController.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Services\AnalyticalService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class AnalyticalController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private AnalyticalService $analytics)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
return view('web.analytical.index', $this->analytics->index($request));
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/Http/Controllers/Api/AccessController.php
Normal file
33
app/Http/Controllers/Api/AccessController.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\RolePermissionService;
|
||||||
|
use App\Services\UserAccessService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
class AccessController extends Controller
|
||||||
|
{
|
||||||
|
public function roles(RolePermissionService $access)
|
||||||
|
{
|
||||||
|
return response()->json($access->index());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function users(UserAccessService $access)
|
||||||
|
{
|
||||||
|
return response()->json($access->index());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePermissions(Request $request, Role $role, RolePermissionService $access)
|
||||||
|
{
|
||||||
|
return response()->json(['role' => $access->updatePermissions($request, $role)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateRoles(Request $request, User $user, UserAccessService $access)
|
||||||
|
{
|
||||||
|
return response()->json(['user' => $access->updateRoles($request, $user)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
app/Http/Controllers/Api/AdminController.php
Normal file
36
app/Http/Controllers/Api/AdminController.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\AdminService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class AdminController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private AdminService $admin)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function info()
|
||||||
|
{
|
||||||
|
return response()->json($this->admin->info());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function phpInfo()
|
||||||
|
{
|
||||||
|
return response($this->admin->phpInfo())->header('Content-Type', 'text/html');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runMigrations()
|
||||||
|
{
|
||||||
|
$result = $this->admin->runMigrations();
|
||||||
|
return response()->json($result, $result['success'] ? 200 : 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmail(Request $request)
|
||||||
|
{
|
||||||
|
$data = $request->validate(['email' => 'required|email']);
|
||||||
|
return response()->json($this->admin->testEmail($data['email']));
|
||||||
|
}
|
||||||
|
}
|
||||||
66
app/Http/Controllers/Api/AuthController.php
Normal file
66
app/Http/Controllers/Api/AuthController.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\AuthService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
use Laravel\Sanctum\PersonalAccessToken;
|
||||||
|
|
||||||
|
class AuthController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private AuthService $authentication)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function login(Request $request)
|
||||||
|
{
|
||||||
|
$credentials = $request->validate(['email' => 'required|email', 'password' => 'required|string']);
|
||||||
|
return response()->json($this->authentication->issueToken($this->authentication->authenticate($credentials)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function token(Request $request): PersonalAccessToken
|
||||||
|
{
|
||||||
|
$token = $request->user()->currentAccessToken();
|
||||||
|
abort_unless($token instanceof PersonalAccessToken, 401, 'Token necessario.');
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->authentication->refresh($this->token($request)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkToken(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->authentication->check($this->token($request)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logout(Request $request)
|
||||||
|
{
|
||||||
|
$this->authentication->revoke($this->token($request));
|
||||||
|
return response()->json(['message' => 'Logout realizado com sucesso', 'revoked' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($request->user()->load('roles.permissions'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendResetLink(Request $request)
|
||||||
|
{
|
||||||
|
$credentials = $request->validate(['email' => 'required|email']);
|
||||||
|
$status = $this->authentication->sendResetLink($credentials);
|
||||||
|
return response()->json(['message' => __($status)], $status === Password::RESET_LINK_SENT ? 200 : 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetPassword(Request $request)
|
||||||
|
{
|
||||||
|
$credentials = $request->validate([
|
||||||
|
'token' => 'required|string', 'email' => 'required|email', 'password' => 'required|string|min:3|confirmed',
|
||||||
|
]);
|
||||||
|
$status = $this->authentication->resetPassword($credentials);
|
||||||
|
return response()->json(['message' => __($status)], $status === Password::PASSWORD_RESET ? 200 : 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
140
app/Http/Controllers/Api/ClientController.php
Normal file
140
app/Http/Controllers/Api/ClientController.php
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\CreateClientRequest;
|
||||||
|
use App\Http\Requests\UpdateClientRequest;
|
||||||
|
use App\Services\ClientService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ClientController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private ClientService $clients)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all clients
|
||||||
|
*
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @since 2025/11
|
||||||
|
* @version 2025/11
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->has('per_page')) {
|
||||||
|
return response()->json(['clients' => $this->clients->listing($request, true)]);
|
||||||
|
}
|
||||||
|
$clients = $this->clients->listing($request)->map(function ($client) {
|
||||||
|
return [
|
||||||
|
'id' => $client->id,
|
||||||
|
'type' => $client->type,
|
||||||
|
'lead_status_id' => $client->lead_status_id,
|
||||||
|
'client_type_id' => $client->client_type_id,
|
||||||
|
'name' => $client->name,
|
||||||
|
'tax_number' => $client->tax_number,
|
||||||
|
'address' => $client->address,
|
||||||
|
'phone' => $client->phone,
|
||||||
|
'email' => $client->email,
|
||||||
|
'seller_id' => $client->seller_id,
|
||||||
|
'status' => $client->status,
|
||||||
|
'notes' => $client->notes,
|
||||||
|
'contact_info' => $client->contact_info,
|
||||||
|
'created_at' => $client->created_at,
|
||||||
|
'updated_at' => $client->updated_at,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json(['clients' => $clients]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply filters to the clients query
|
||||||
|
*
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @since 2025/11
|
||||||
|
* @version 2025/11
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Show a single client
|
||||||
|
*
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @since 2025/11
|
||||||
|
* @version 2025/11
|
||||||
|
*/
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
return response()->json($this->clients->find($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a new client
|
||||||
|
*
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @since 2025/11
|
||||||
|
* @version 2025/11
|
||||||
|
*/
|
||||||
|
public function store(CreateClientRequest $request)
|
||||||
|
{
|
||||||
|
$client = $this->clients->create($request->validated());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Client created successfully',
|
||||||
|
'client' => $client
|
||||||
|
], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing client
|
||||||
|
*
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @since 2025/11
|
||||||
|
* @version 2025/11
|
||||||
|
*/
|
||||||
|
public function update(UpdateClientRequest $request, $id)
|
||||||
|
{
|
||||||
|
$client = $this->clients->update($id, $request->validated());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Client updated successfully',
|
||||||
|
'client' => $client
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a client (soft delete by changing status)
|
||||||
|
*
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @since 2025/11
|
||||||
|
* @version 2025/11
|
||||||
|
*/
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$this->clients->delete($id);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Client deleted successfully']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function options(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($request->filled('type')
|
||||||
|
? $this->clients->form($request->input('type'), $request->user())
|
||||||
|
: ['clientTypes' => $this->clients->types()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detail($id)
|
||||||
|
{
|
||||||
|
return response()->json($this->clients->detail($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchName(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->clients->searchName($request->input('term', '')));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchID(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->clients->searchID($request->input('id')));
|
||||||
|
}
|
||||||
|
}
|
||||||
92
app/Http/Controllers/Api/ContractController.php
Normal file
92
app/Http/Controllers/Api/ContractController.php
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\ContractRequest;
|
||||||
|
use App\Http\Requests\RenewContractRequest;
|
||||||
|
use App\Services\ContractService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ContractController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private ContractService $contracts)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->index($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function options(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->create($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(string $id)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->show($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->edit($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(ContractRequest $request)
|
||||||
|
{
|
||||||
|
return response()->json(['contract' => $this->contracts->store($request)], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(ContractRequest $request, string $id)
|
||||||
|
{
|
||||||
|
return response()->json(['contract' => $this->contracts->update($request, $id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renewalOptions(string $id)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->renewForm($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renew(RenewContractRequest $request, $id)
|
||||||
|
{
|
||||||
|
return response()->json(['contract' => $this->contracts->renew($request, $id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function history($id)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->history($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showHistory($id, $historyId)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->editHistory($id, $historyId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateHistory(Request $request, $id, $historyId)
|
||||||
|
{
|
||||||
|
return response()->json(['history' => $this->contracts->updateHistory($request, $id, $historyId)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyHistory($id, $historyId)
|
||||||
|
{
|
||||||
|
$this->contracts->destroyHistory($id, $historyId);
|
||||||
|
return response()->json(['message' => 'Registo eliminado com sucesso.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->destroy($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bulkDestroy(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->bulkDestroy($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function export(Request $request)
|
||||||
|
{
|
||||||
|
return $this->contracts->export($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/Http/Controllers/Api/DashboardController.php
Normal file
21
app/Http/Controllers/Api/DashboardController.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\HomeService;
|
||||||
|
use App\Services\AnalyticalService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class DashboardController extends Controller
|
||||||
|
{
|
||||||
|
public function index(HomeService $dashboard)
|
||||||
|
{
|
||||||
|
return response()->json($dashboard->index());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analytics(Request $request, AnalyticalService $analytics)
|
||||||
|
{
|
||||||
|
return response()->json($analytics->index($request));
|
||||||
|
}
|
||||||
|
}
|
||||||
49
app/Http/Controllers/Api/LeadController.php
Normal file
49
app/Http/Controllers/Api/LeadController.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\LeadRequest;
|
||||||
|
use App\Services\LeadService;
|
||||||
|
|
||||||
|
class LeadController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private LeadService $leads)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return response()->json($this->leads->list());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function options()
|
||||||
|
{
|
||||||
|
return response()->json($this->leads->create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
return response()->json($this->leads->show($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(LeadRequest $request)
|
||||||
|
{
|
||||||
|
return response()->json(['lead' => $this->leads->store($request)], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(LeadRequest $request, $id)
|
||||||
|
{
|
||||||
|
return response()->json(['lead' => $this->leads->update($request, $id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convert($id)
|
||||||
|
{
|
||||||
|
return response()->json(['client' => $this->leads->convert($id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
return response()->json($this->leads->destroy($id));
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Http/Controllers/Api/ResourceController.php
Normal file
40
app/Http/Controllers/Api/ResourceController.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\ResourceService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ResourceController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private ResourceService $resources)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(string $resource)
|
||||||
|
{
|
||||||
|
return response()->json(['data' => $this->resources->all($resource)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(string $resource, $id)
|
||||||
|
{
|
||||||
|
return response()->json(['data' => $this->resources->find($resource, $id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request, string $resource)
|
||||||
|
{
|
||||||
|
return response()->json(['data' => $this->resources->save($resource, $request)], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, string $resource, $id)
|
||||||
|
{
|
||||||
|
return response()->json(['data' => $this->resources->save($resource, $request, $id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(string $resource, $id)
|
||||||
|
{
|
||||||
|
$this->resources->delete($resource, $id);
|
||||||
|
return response()->json(['message' => 'Registo eliminado com sucesso.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
app/Http/Controllers/Api/UserController.php
Normal file
63
app/Http/Controllers/Api/UserController.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\UserService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
|
||||||
|
class UserController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private UserService $users)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return response()->json($this->users->index());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function options()
|
||||||
|
{
|
||||||
|
return response()->json($this->users->create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(User $user)
|
||||||
|
{
|
||||||
|
return response()->json($this->users->edit($user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json(['user' => $this->users->store($request)], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, User $user)
|
||||||
|
{
|
||||||
|
return response()->json(['user' => $this->users->update($request, $user)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(User $user)
|
||||||
|
{
|
||||||
|
$this->users->destroy($user);
|
||||||
|
return response()->json(['message' => 'Utilizador removido com sucesso.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendPasswordResetLink(User $user)
|
||||||
|
{
|
||||||
|
$status = $this->users->sendPasswordResetLink($user);
|
||||||
|
return response()->json(['message' => __($status)], $status === Password::RESET_LINK_SENT ? 200 : 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function savePin(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->users->savePin($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkPin(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->users->checkPin($request));
|
||||||
|
}
|
||||||
|
}
|
||||||
77
app/Http/Controllers/Auth/ApiLoginController.php
Normal file
77
app/Http/Controllers/Auth/ApiLoginController.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\AuthService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
|
|
||||||
|
class ApiLoginController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private AuthService $authentication)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @since 2025/11
|
||||||
|
* @version 2025/11
|
||||||
|
*/
|
||||||
|
public function showLoginForm()
|
||||||
|
{
|
||||||
|
// Prevent proxies/CDN from caching a page with a CSRF token tied to another visitor's session
|
||||||
|
return response()->view('auth.login')
|
||||||
|
->header('Cache-Control', 'no-store, no-cache, must-revalidate, private')
|
||||||
|
->header('Pragma', 'no-cache');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @since 2025/11
|
||||||
|
* @version 2025/11
|
||||||
|
*/
|
||||||
|
public function login(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'email' => 'required|email',
|
||||||
|
'password' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$user = $this->authentication->authenticate($request->only('email', 'password'));
|
||||||
|
} catch (HttpException $exception) {
|
||||||
|
return back()->withInput($request->only('email'))->with('error', 'Credenciais invalidas.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->authentication->issueToken($user);
|
||||||
|
|
||||||
|
Auth::login($user);
|
||||||
|
$request->session()->regenerate();
|
||||||
|
|
||||||
|
session([
|
||||||
|
'api_token' => $data['token'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('home');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @since 2025/11
|
||||||
|
* @version 2025/11
|
||||||
|
*/
|
||||||
|
public function logout(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->user() && session('api_token')) {
|
||||||
|
$this->authentication->revoke($this->authentication->sessionToken($request->user(), session('api_token')));
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth::logout();
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
return redirect()->route('login');
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/Http/Controllers/Auth/ConfirmPasswordController.php
Normal file
39
app/Http/Controllers/Auth/ConfirmPasswordController.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users when the intended url fails.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = '/home';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/Http/Controllers/Auth/ForgotPasswordController.php
Normal file
34
app/Http/Controllers/Auth/ForgotPasswordController.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||||
|
use App\Services\AuthService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public function sendResetLinkEmail(Request $request, AuthService $authentication)
|
||||||
|
{
|
||||||
|
$credentials = $request->validate(['email' => 'required|email']);
|
||||||
|
$status = $authentication->sendResetLink($credentials);
|
||||||
|
return $status === Password::RESET_LINK_SENT
|
||||||
|
? back()->with('status', __($status))
|
||||||
|
: back()->withErrors(['email' => __($status)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Http/Controllers/Auth/LoginController.php
Normal file
40
app/Http/Controllers/Auth/LoginController.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after login.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = '/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('guest')->except('logout');
|
||||||
|
$this->middleware('auth')->only('logout');
|
||||||
|
}
|
||||||
|
}
|
||||||
72
app/Http/Controllers/Auth/RegisterController.php
Normal file
72
app/Http/Controllers/Auth/RegisterController.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class RegisterController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Register Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller handles the registration of new users as well as their
|
||||||
|
| validation and creation. By default this controller uses a trait to
|
||||||
|
| provide this functionality without requiring any additional code.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use RegistersUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after registration.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = '/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('guest');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a validator for an incoming registration request.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return \Illuminate\Contracts\Validation\Validator
|
||||||
|
*/
|
||||||
|
protected function validator(array $data)
|
||||||
|
{
|
||||||
|
return Validator::make($data, [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||||
|
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new user instance after a valid registration.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return \App\Models\User
|
||||||
|
*/
|
||||||
|
protected function create(array $data)
|
||||||
|
{
|
||||||
|
return User::create([
|
||||||
|
'name' => $data['name'],
|
||||||
|
'email' => $data['email'],
|
||||||
|
'password' => Hash::make($data['password']),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/Http/Controllers/Auth/ResetPasswordController.php
Normal file
50
app/Http/Controllers/Auth/ResetPasswordController.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||||
|
use App\Services\AuthService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after resetting their password.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = '/';
|
||||||
|
|
||||||
|
public function reset(Request $request, AuthService $authentication)
|
||||||
|
{
|
||||||
|
$credentials = $request->validate($this->rules());
|
||||||
|
$status = $authentication->resetPassword($credentials);
|
||||||
|
return $status === Password::PASSWORD_RESET
|
||||||
|
? redirect()->route('login')->with('status', __($status))
|
||||||
|
: back()->withErrors(['email' => __($status)])->withInput($request->only('email'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'token' => 'required',
|
||||||
|
'email' => 'required|email',
|
||||||
|
'password' => ['required', 'string', 'min:3', 'confirmed'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
41
app/Http/Controllers/Auth/VerificationController.php
Normal file
41
app/Http/Controllers/Auth/VerificationController.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Foundation\Auth\VerifiesEmails;
|
||||||
|
|
||||||
|
class VerificationController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Email Verification Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller is responsible for handling email verification for any
|
||||||
|
| user that recently registered with the application. Emails may also
|
||||||
|
| be re-sent if the user didn't receive the original email message.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use VerifiesEmails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after verification.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = '/home';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
$this->middleware('signed')->only('verify');
|
||||||
|
$this->middleware('throttle:6,1')->only('verify', 'resend');
|
||||||
|
}
|
||||||
|
}
|
||||||
79
app/Http/Controllers/ClientController.php
Normal file
79
app/Http/Controllers/ClientController.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\CreateClientRequest;
|
||||||
|
use App\Http\Requests\UpdateClientRequest;
|
||||||
|
use App\Services\ClientService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ClientController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private ClientService $clients)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(Request $request)
|
||||||
|
{
|
||||||
|
$clients = $this->clients->listing($request, true);
|
||||||
|
$clientTypes = $this->clients->types();
|
||||||
|
|
||||||
|
return view('web.clients.list', compact('clients', 'clientTypes'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$this->clients->delete($id);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'description' => 'Cliente eliminado com sucesso',
|
||||||
|
'status' => 'success'
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add()
|
||||||
|
{
|
||||||
|
$clientTypes = $this->clients->types();
|
||||||
|
|
||||||
|
return view('web.clients.create.add', compact('clientTypes'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add2step(Request $request, $type)
|
||||||
|
{
|
||||||
|
return view('web.clients.create.form', $this->clients->form($type, $request->user()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(CreateClientRequest $request)
|
||||||
|
{
|
||||||
|
$client = $this->clients->create($request->validated());
|
||||||
|
|
||||||
|
return redirect()->route('client.detail', ['id' => $client->id])
|
||||||
|
->with('success', 'Cliente criado com sucesso!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detail($clientId)
|
||||||
|
{
|
||||||
|
return view('web.clients.detail', $this->clients->detail($clientId));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update(UpdateClientRequest $request, $id)
|
||||||
|
{
|
||||||
|
$this->clients->update($id, $request->validated());
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Cliente atualizado com sucesso!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchName(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->clients->searchName($request->input('term', '')));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchID(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->clients->searchID($request->input('id')));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
107
app/Http/Controllers/ContractController.php
Normal file
107
app/Http/Controllers/ContractController.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\ContractRequest;
|
||||||
|
use App\Http\Requests\RenewContractRequest;
|
||||||
|
use App\Services\ContractService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ContractController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private ContractService $contracts = new ContractService())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
return view('web.contract.list', $this->contracts->index($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request)
|
||||||
|
{
|
||||||
|
return view('web.contract.add_edit', $this->contracts->create($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(ContractRequest $request)
|
||||||
|
{
|
||||||
|
$contract = $this->contracts->store($request);
|
||||||
|
return redirect()->route('client.detail', ['id' => $contract->client_id])->with('success', 'Contrato criado com sucesso!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
return view('web.contract.add_edit', $this->contracts->edit($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(ContractRequest $request, string $id)
|
||||||
|
{
|
||||||
|
$this->contracts->update($request, $id);
|
||||||
|
return redirect()->route('contract.list')->with('success', 'Contrato atualizado com sucesso!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(string $id)
|
||||||
|
{
|
||||||
|
return view('web.contract.detail', $this->contracts->show($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renewForm(string $id)
|
||||||
|
{
|
||||||
|
return view('web.contract.renew', $this->contracts->renewForm($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renew(RenewContractRequest $request, $id)
|
||||||
|
{
|
||||||
|
$contract = $this->contracts->renew($request, $id);
|
||||||
|
return redirect()->route('contract.edit', ['id' => $contract->id])->with('success', 'Contrato renovado com sucesso!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function history($id)
|
||||||
|
{
|
||||||
|
return view('web.contract.history', $this->contracts->history($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createHistory($id)
|
||||||
|
{
|
||||||
|
return view('web.contract.history_create', $this->contracts->createHistory($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeHistory(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->contracts->storeHistory($request, $id);
|
||||||
|
return redirect()->route('contract.history', $id)->with('success', 'Registo do histórico criado com sucesso.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function editHistory($id, $historyId)
|
||||||
|
{
|
||||||
|
return view('web.contract.history_edit', $this->contracts->editHistory($id, $historyId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateHistory(Request $request, $id, $historyId)
|
||||||
|
{
|
||||||
|
$this->contracts->updateHistory($request, $id, $historyId);
|
||||||
|
return redirect()->route('contract.history', $id)->with('success', 'Registo do histórico atualizado com sucesso.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyHistory($id, $historyId)
|
||||||
|
{
|
||||||
|
$this->contracts->destroyHistory($id, $historyId);
|
||||||
|
return redirect()->route('contract.history', $id)->with('success', 'Registo do histórico eliminado com sucesso.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function export(Request $request)
|
||||||
|
{
|
||||||
|
return $this->contracts->export($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Request $request, string $id)
|
||||||
|
{
|
||||||
|
$result = $this->contracts->destroy($id);
|
||||||
|
return $request->ajax() ? response()->json($result) : back()->with('success', $result['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bulkDestroy(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->contracts->bulkDestroy($request));
|
||||||
|
}
|
||||||
|
}
|
||||||
12
app/Http/Controllers/Controller.php
Normal file
12
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
|
||||||
|
class Controller extends BaseController
|
||||||
|
{
|
||||||
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
|
}
|
||||||
61
app/Http/Controllers/EmailTestController.php
Normal file
61
app/Http/Controllers/EmailTestController.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\View;
|
||||||
|
|
||||||
|
class EmailTestController extends Controller
|
||||||
|
{
|
||||||
|
public function sendTestEmail()
|
||||||
|
{
|
||||||
|
$start = microtime(true);
|
||||||
|
|
||||||
|
$emailData = [
|
||||||
|
'to' => 'luismedon@gmail.com',
|
||||||
|
'subject' => 'Teste Completo de Envio de E-mail',
|
||||||
|
'body' => 'Este é um e-mail de teste completo enviado via Laravel.',
|
||||||
|
'status' => 'pendente',
|
||||||
|
'error' => null,
|
||||||
|
'sent_at' => now()->toDateTimeString(),
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
Mail::raw($emailData['body'], function ($message) use (&$emailData) {
|
||||||
|
$message->to($emailData['to'])
|
||||||
|
->subject($emailData['subject']);
|
||||||
|
});
|
||||||
|
|
||||||
|
$emailData['status'] = 'enviado com sucesso';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$emailData['status'] = 'falha ao enviar';
|
||||||
|
$emailData['error'] = $e->getMessage();
|
||||||
|
Log::error('Erro ao enviar e-mail de teste: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$emailData['execution_time'] = round(microtime(true) - $start, 4);
|
||||||
|
|
||||||
|
$emailData['mail_config'] = [
|
||||||
|
'driver' => config('mail.default'),
|
||||||
|
'host' => config('mail.mailers.smtp.host'),
|
||||||
|
'port' => config('mail.mailers.smtp.port'),
|
||||||
|
'encryption' => config('mail.mailers.smtp.encryption'),
|
||||||
|
'username' => config('mail.mailers.smtp.username'),
|
||||||
|
'from' => config('mail.from.address'),
|
||||||
|
'from_name' => config('mail.from.name'),
|
||||||
|
'timeout' => config('mail.mailers.smtp.timeout') ?? 'padrão',
|
||||||
|
];
|
||||||
|
|
||||||
|
$emailData['laravel_info'] = [
|
||||||
|
'laravel_version' => app()->version(),
|
||||||
|
'php_version' => phpversion(),
|
||||||
|
'env' => App::environment(),
|
||||||
|
'mailer_class' => get_class(app()->make('mailer')->getSymfonyTransport()),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('emails.test-status', compact('emailData'));
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/Http/Controllers/HomeController.php
Normal file
17
app/Http/Controllers/HomeController.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Services\HomeService;
|
||||||
|
|
||||||
|
class HomeController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private HomeService $dashboard)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('home', $this->dashboard->index());
|
||||||
|
}
|
||||||
|
}
|
||||||
56
app/Http/Controllers/LeadController.php
Normal file
56
app/Http/Controllers/LeadController.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\LeadRequest;
|
||||||
|
use App\Services\LeadService;
|
||||||
|
|
||||||
|
class LeadController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private LeadService $leads)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list()
|
||||||
|
{
|
||||||
|
return view('web.lead.list', $this->leads->list());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
return view('web.lead.show', $this->leads->show($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
return view('web.lead.add_edit', $this->leads->create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
return view('web.lead.add_edit', $this->leads->edit($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(LeadRequest $request)
|
||||||
|
{
|
||||||
|
$this->leads->store($request);
|
||||||
|
return redirect()->route('lead.list')->with('success', 'Lead criado com sucesso!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(LeadRequest $request, $id)
|
||||||
|
{
|
||||||
|
$this->leads->update($request, $id);
|
||||||
|
return redirect()->route('lead.list')->with('success', 'Lead atualizado com sucesso!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convert($id)
|
||||||
|
{
|
||||||
|
$this->leads->convert($id);
|
||||||
|
return redirect()->route('lead.list')->with('success', 'Lead successfully converted to client!');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
return response()->json($this->leads->destroy($id));
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/Http/Controllers/Resources/ContractStatusController.php
Normal file
11
app/Http/Controllers/Resources/ContractStatusController.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Resources;
|
||||||
|
|
||||||
|
class ContractStatusController extends ResourceController
|
||||||
|
{
|
||||||
|
protected string $resource = 'contract-statuses';
|
||||||
|
protected string $viewPath = 'web.resources.contract_status';
|
||||||
|
protected string $collectionName = 'statuses';
|
||||||
|
protected string $itemName = 'contractStatus';
|
||||||
|
}
|
||||||
11
app/Http/Controllers/Resources/ContractTypeController.php
Normal file
11
app/Http/Controllers/Resources/ContractTypeController.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Resources;
|
||||||
|
|
||||||
|
class ContractTypeController extends ResourceController
|
||||||
|
{
|
||||||
|
protected string $resource = 'contract_types';
|
||||||
|
protected string $viewPath = 'web.resources.contract_types';
|
||||||
|
protected string $collectionName = 'types';
|
||||||
|
protected string $itemName = 'contract_type';
|
||||||
|
}
|
||||||
11
app/Http/Controllers/Resources/CuiTierController.php
Normal file
11
app/Http/Controllers/Resources/CuiTierController.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Resources;
|
||||||
|
|
||||||
|
class CuiTierController extends ResourceController
|
||||||
|
{
|
||||||
|
protected string $resource = 'cui_tiers';
|
||||||
|
protected string $viewPath = 'web.resources.cui_tiers';
|
||||||
|
protected string $collectionName = 'cuiTiers';
|
||||||
|
protected string $itemName = 'cuiTier';
|
||||||
|
}
|
||||||
11
app/Http/Controllers/Resources/PaymentMethodController.php
Normal file
11
app/Http/Controllers/Resources/PaymentMethodController.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Resources;
|
||||||
|
|
||||||
|
class PaymentMethodController extends ResourceController
|
||||||
|
{
|
||||||
|
protected string $resource = 'payment_methods';
|
||||||
|
protected string $viewPath = 'web.resources.payment_methods';
|
||||||
|
protected string $collectionName = 'methods';
|
||||||
|
protected string $itemName = 'payment_method';
|
||||||
|
}
|
||||||
11
app/Http/Controllers/Resources/PowerTierController.php
Normal file
11
app/Http/Controllers/Resources/PowerTierController.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Resources;
|
||||||
|
|
||||||
|
class PowerTierController extends ResourceController
|
||||||
|
{
|
||||||
|
protected string $resource = 'power_tiers';
|
||||||
|
protected string $viewPath = 'web.resources.power_tiers';
|
||||||
|
protected string $collectionName = 'tiers';
|
||||||
|
protected string $itemName = 'power_tier';
|
||||||
|
}
|
||||||
57
app/Http/Controllers/Resources/ResourceController.php
Normal file
57
app/Http/Controllers/Resources/ResourceController.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Resources;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\ResourceService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
abstract class ResourceController extends Controller
|
||||||
|
{
|
||||||
|
protected string $resource;
|
||||||
|
protected string $viewPath;
|
||||||
|
protected string $collectionName;
|
||||||
|
protected string $itemName;
|
||||||
|
|
||||||
|
public function __construct(private ResourceService $resources)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view($this->viewPath . '.index', [$this->collectionName => $this->resources->all($this->resource)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
return view($this->viewPath . '.add_edit');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
return $this->edit($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
return view($this->viewPath . '.add_edit', [$this->itemName => $this->resources->find($this->resource, $id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$this->resources->save($this->resource, $request);
|
||||||
|
return redirect()->route($this->resource . '.index')->with('success', 'Registo criado com sucesso.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->resources->save($this->resource, $request, $id);
|
||||||
|
return redirect()->route($this->resource . '.index')->with('success', 'Registo atualizado com sucesso.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$this->resources->delete($this->resource, $id);
|
||||||
|
return redirect()->route($this->resource . '.index')->with('success', 'Registo eliminado com sucesso.');
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/Http/Controllers/Resources/SupplierController.php
Normal file
11
app/Http/Controllers/Resources/SupplierController.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Resources;
|
||||||
|
|
||||||
|
class SupplierController extends ResourceController
|
||||||
|
{
|
||||||
|
protected string $resource = 'suppliers';
|
||||||
|
protected string $viewPath = 'web.resources.suppliers';
|
||||||
|
protected string $collectionName = 'suppliers';
|
||||||
|
protected string $itemName = 'supplier';
|
||||||
|
}
|
||||||
11
app/Http/Controllers/Resources/TypologyController.php
Normal file
11
app/Http/Controllers/Resources/TypologyController.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Resources;
|
||||||
|
|
||||||
|
class TypologyController extends ResourceController
|
||||||
|
{
|
||||||
|
protected string $resource = 'typologies';
|
||||||
|
protected string $viewPath = 'web.resources.typology';
|
||||||
|
protected string $collectionName = 'typologies';
|
||||||
|
protected string $itemName = 'typology';
|
||||||
|
}
|
||||||
25
app/Http/Controllers/RolePermissionController.php
Normal file
25
app/Http/Controllers/RolePermissionController.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Services\RolePermissionService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
class RolePermissionController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private RolePermissionService $access)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('web.admin.access.roles_permissions', $this->access->index());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePermissions(Request $request, Role $role)
|
||||||
|
{
|
||||||
|
$this->access->updatePermissions($request, $role);
|
||||||
|
return back()->with('success', 'Permissoes atualizadas.');
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Http/Controllers/UserAccessController.php
Normal file
25
app/Http/Controllers/UserAccessController.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\UserAccessService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserAccessController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private UserAccessService $access)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('web.admin.access.users', $this->access->index());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateRoles(Request $request, User $user)
|
||||||
|
{
|
||||||
|
$this->access->updateRoles($request, $user);
|
||||||
|
return back()->with('success', 'Roles atualizadas.');
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/Http/Controllers/UserController.php
Normal file
65
app/Http/Controllers/UserController.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\UserService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
|
||||||
|
class UserController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private UserService $users)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('web.admin.user.index', $this->users->index());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
return view('auth.register', $this->users->create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$this->users->store($request);
|
||||||
|
return redirect()->route('users.index')->with('success', 'Utilizador criado com sucesso.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(User $user)
|
||||||
|
{
|
||||||
|
return view('web.admin.user.add_edit', $this->users->edit($user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, User $user)
|
||||||
|
{
|
||||||
|
$this->users->update($request, $user);
|
||||||
|
return redirect()->route('users.index')->with('success', 'Utilizador atualizado com sucesso.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(User $user)
|
||||||
|
{
|
||||||
|
$this->users->destroy($user);
|
||||||
|
return redirect()->route('users.index')->with('success', 'Utilizador removido com sucesso.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendPasswordResetLink(User $user)
|
||||||
|
{
|
||||||
|
$status = $this->users->sendPasswordResetLink($user);
|
||||||
|
return redirect()->route('users.edit', $user)->with($status === Password::RESET_LINK_SENT ? 'success' : 'error', __($status));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function savePin(Request $request)
|
||||||
|
{
|
||||||
|
$result = $this->users->savePin($request);
|
||||||
|
return back()->with('success', $result['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkPin(Request $request)
|
||||||
|
{
|
||||||
|
return response()->json($this->users->checkPin($request));
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Http/Kernel.php
Normal file
40
app/Http/Kernel.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
|
|
||||||
|
class Kernel extends HttpKernel
|
||||||
|
{
|
||||||
|
protected $middleware = [
|
||||||
|
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
|
||||||
|
\Illuminate\Http\Middleware\TrustProxies::class,
|
||||||
|
\Illuminate\Http\Middleware\HandleCors::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $middlewareGroups = [
|
||||||
|
'web' => [
|
||||||
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
'api' => [
|
||||||
|
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||||
|
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $middlewareAliases = [
|
||||||
|
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
|
||||||
|
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
|
||||||
|
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
|
||||||
|
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||||
|
'auth.session' => \App\Http\Middleware\AuthSession::class
|
||||||
|
];
|
||||||
|
}
|
||||||
45
app/Http/Middleware/ApiResponse.php
Normal file
45
app/Http/Middleware/ApiResponse.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ApiResponse
|
||||||
|
{
|
||||||
|
private const FINANCIAL_FIELDS = [
|
||||||
|
'contract_value', 'commission_value', 'profit_value', 'chargeback_value', 'chargeback_date',
|
||||||
|
'paid', 'chargeback', 'contract_snapshot', 'payments', 'installments',
|
||||||
|
'totalPvp', 'totalCommission', 'totalChargeback', 'totalMargin', 'totalReceived', 'totalPending',
|
||||||
|
'commission', 'profit', 'pvp', 'net', 'commission_rate', 'amount', 'profit_margin', 'total_installments', 'paid_installments',
|
||||||
|
'total_vendas', 'total_lucro', 'total_comissao', 'vendasMensais', 'vendasPorTipo',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
$request->headers->set('Accept', 'application/json');
|
||||||
|
$response = $next($request);
|
||||||
|
if ($response instanceof JsonResponse && $request->is('api/contracts*', 'api/clients*', 'api/dashboard', 'api/analytics', 'api/leads*')
|
||||||
|
&& $request->user() && !$request->user()->can('view_contract_summary')) {
|
||||||
|
$response->setData($this->withoutFinancials($response->getData(true)));
|
||||||
|
}
|
||||||
|
$response->headers->set('Cache-Control', 'no-store, private');
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function withoutFinancials($data)
|
||||||
|
{
|
||||||
|
if (!is_array($data)) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
if (in_array($key, self::FINANCIAL_FIELDS, true)) {
|
||||||
|
unset($data[$key]);
|
||||||
|
} else {
|
||||||
|
$data[$key] = $this->withoutFinancials($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Http/Middleware/AuthSession.php
Normal file
31
app/Http/Middleware/AuthSession.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Services\AuthService;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class AuthSession
|
||||||
|
{
|
||||||
|
public function __construct(private AuthService $authentication)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$plainText = $request->session()->get('api_token');
|
||||||
|
$token = $user && $plainText ? $this->authentication->sessionToken($user, $plainText) : null;
|
||||||
|
if (!$token) {
|
||||||
|
Auth::logout();
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
return redirect()->route('login');
|
||||||
|
}
|
||||||
|
$result = $this->authentication->refresh($token);
|
||||||
|
$request->session()->put('token_expires_at', $result['expires_at']);
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/Http/Middleware/EnsureApiUser.php
Normal file
15
app/Http/Middleware/EnsureApiUser.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EnsureApiUser
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
abort_if($request->user()->can_login === false, 401, 'Conta desativada.');
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
196
app/Http/Requests/ContractRequest.php
Normal file
196
app/Http/Requests/ContractRequest.php
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Validator;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class ContractRequest extends FormRequest
|
||||||
|
{
|
||||||
|
protected function localizedMessage(string $key, string $fallback): string
|
||||||
|
{
|
||||||
|
if (function_exists('app') && app()->bound('translator')) {
|
||||||
|
return __($key, [], app()->getLocale()) ?: $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
foreach (['validity', 'contract_end_date', 'contract_date', 'activation_date', 'chargeback_date', 'payment_date', 'renewal_date'] as $field) {
|
||||||
|
$value = $this->input($field);
|
||||||
|
|
||||||
|
if ($value) {
|
||||||
|
try {
|
||||||
|
$date = str_contains((string) $value, '/')
|
||||||
|
? Carbon::createFromFormat('d/m/Y', $value)
|
||||||
|
: Carbon::parse($value);
|
||||||
|
$this->merge([$field => $date->format('Y-m-d')]);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// Mantém o valor original para o validador apresentar a mensagem adequada.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->filled('chargeback_value')) {
|
||||||
|
$this->merge(['chargeback' => true]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'client_id' => ['required', 'exists:clients,id'],
|
||||||
|
'contract_value' => ['nullable', 'numeric', 'min:0'],
|
||||||
|
'commission_value' => ['nullable', 'numeric',],
|
||||||
|
'profit_value' => ['nullable', 'numeric'],
|
||||||
|
'seller_id' => ['nullable', 'exists:users,id'],
|
||||||
|
'supply_type' => ['required', 'in:cpe,cui'],
|
||||||
|
'supply_address' => ['nullable', 'string', 'max:255'],
|
||||||
|
'annual_consumption' => ['nullable', 'numeric', 'min:0'],
|
||||||
|
'code' => ['required', 'string', 'max:255'],
|
||||||
|
'contract_type_id' => ['required', 'exists:contract_types,id'],
|
||||||
|
'validity' => ['nullable', 'date'],
|
||||||
|
'contract_end_date' => ['nullable', 'date'],
|
||||||
|
'contract_date' => ['nullable', 'date'],
|
||||||
|
'activation_date' => ['nullable', 'date'],
|
||||||
|
'status_id' => ['required', 'exists:contract_status,id'],
|
||||||
|
'commission_rate' => ['nullable', 'numeric', 'between:0,100'],
|
||||||
|
'notes' => ['nullable', 'string'],
|
||||||
|
'power_tier_id' => ['nullable', 'exists:power_tiers,id'],
|
||||||
|
'payment_method_id' => ['required', 'exists:payment_methods,id'],
|
||||||
|
'delivery_method' => ['required', 'in:correio,email'],
|
||||||
|
'delivery_email' => ['nullable', 'email', 'max:255'],
|
||||||
|
'previous_supplier_id' => ['nullable', 'exists:suppliers,id'],
|
||||||
|
'new_supplier_id' => ['nullable', 'exists:suppliers,id'],
|
||||||
|
'typology_id' => ['nullable', 'exists:typologies,id'],
|
||||||
|
'cui_tier_id' => ['nullable', 'exists:cui_tiers,id'],
|
||||||
|
'go_ki' => ['required', 'numeric'],
|
||||||
|
'paid' => ['nullable', 'boolean'],
|
||||||
|
'chargeback' => ['nullable', 'boolean'],
|
||||||
|
'chargeback_value' => ['required_if:chargeback,1', 'nullable', 'numeric', 'max:-0.01'],
|
||||||
|
'chargeback_date' => ['required_if:chargeback,1', 'nullable', 'date'],
|
||||||
|
|
||||||
|
// Parcelas
|
||||||
|
'installments.*.due_date' => ['nullable', 'date'],
|
||||||
|
'installments.*.amount' => ['nullable', 'numeric', 'min:0'],
|
||||||
|
'installments.*.paid' => ['nullable', 'boolean'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->user()?->can('view_contract_summary')) {
|
||||||
|
foreach (['contract_value', 'commission_value', 'profit_value', 'paid', 'chargeback', 'chargeback_value', 'chargeback_date', 'installments.*.due_date', 'installments.*.amount', 'installments.*.paid'] as $field) {
|
||||||
|
unset($rules[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withValidator(Validator $validator)
|
||||||
|
{
|
||||||
|
$validator->after(function ($validator) {
|
||||||
|
if (! $this->user()?->can('view_contract_summary')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$installments = $this->input('installments', []);
|
||||||
|
$validity = $this->input('validity');
|
||||||
|
|
||||||
|
$previousDate = null;
|
||||||
|
foreach ($installments as $index => $installment) {
|
||||||
|
if (!empty($installment['due_date'])) {
|
||||||
|
$dueDate = Carbon::parse($installment['due_date']);
|
||||||
|
|
||||||
|
// Data da parcela não pode ser superior à validade do contrato
|
||||||
|
// if (!empty($validity)) {
|
||||||
|
// $validityDate = Carbon::createFromFormat('d/m/Y', $validity);
|
||||||
|
// if ($dueDate->gt($validityDate)) {
|
||||||
|
// $validator->errors()->add(
|
||||||
|
// "installments.$index.due_date",
|
||||||
|
// "A data da parcela $index não pode ser superior à data de renovação do contrato."
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Data da parcela deve ser superior à anterior
|
||||||
|
if ($previousDate && $dueDate->lte($previousDate)) {
|
||||||
|
$validator->errors()->add(
|
||||||
|
"installments.$index.due_date",
|
||||||
|
"A data da parcela $index deve ser superior à parcela anterior."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$previousDate = $dueDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'cui_tier_id.exists' => $this->localizedMessage('validation.custom.cui_tier_id.exists', 'O escalão CUI selecionado não é válido.'),
|
||||||
|
'client_id.required' => $this->localizedMessage('validation.custom.client_id.required', 'O cliente é obrigatório.'),
|
||||||
|
'client_id.exists' => $this->localizedMessage('validation.custom.client_id.exists', 'O cliente selecionado não é válido.'),
|
||||||
|
'contract_value.numeric' => $this->localizedMessage('validation.custom.contract_value.numeric', 'O valor do contrato deve ser um número.'),
|
||||||
|
'contract_value.min' => $this->localizedMessage('validation.custom.contract_value.min', 'O valor do contrato não pode ser negativo.'),
|
||||||
|
'go_ki.required' => $this->localizedMessage('validation.custom.go_ki.required', 'O campo GO/KI é obrigatório.'),
|
||||||
|
'go_ki.numeric' => $this->localizedMessage('validation.custom.go_ki.numeric', 'O campo GO/KI deve ser numérico.'),
|
||||||
|
'commission_value.numeric' => $this->localizedMessage('validation.custom.commission_value.numeric', 'O valor da comissão deve ser um número.'),
|
||||||
|
'profit_value.numeric' => $this->localizedMessage('validation.custom.profit_value.numeric', 'O lucro deve ser um número.'),
|
||||||
|
'seller_id.exists' => $this->localizedMessage('validation.custom.seller_id.exists', 'O vendedor selecionado não é válido.'),
|
||||||
|
'supply_type.required' => $this->localizedMessage('validation.custom.supply_type.required', 'O tipo de fornecimento é obrigatório.'),
|
||||||
|
'supply_type.in' => $this->localizedMessage('validation.custom.supply_type.in', 'O tipo de fornecimento deve ser CPE ou cui.'),
|
||||||
|
'supply_address.string' => $this->localizedMessage('validation.custom.supply_address.string', 'O endereço de fornecimento deve ser texto.'),
|
||||||
|
'supply_address.max' => $this->localizedMessage('validation.custom.supply_address.max', 'O endereço de fornecimento não pode exceder 255 caracteres.'),
|
||||||
|
'annual_consumption.numeric' => $this->localizedMessage('validation.custom.annual_consumption.numeric', 'O consumo anual deve ser numérico.'),
|
||||||
|
'annual_consumption.min' => $this->localizedMessage('validation.custom.annual_consumption.min', 'O consumo anual não pode ser negativo.'),
|
||||||
|
'contract_type_id.required' => $this->localizedMessage('validation.custom.contract_type_id.required', 'O tipo de contrato é obrigatório.'),
|
||||||
|
'contract_type_id.exists' => $this->localizedMessage('validation.custom.contract_type_id.exists', 'O tipo de contrato selecionado não é válido.'),
|
||||||
|
'validity.date' => $this->localizedMessage('validation.custom.validity.date', 'A validade deve ser uma data válida.'),
|
||||||
|
'validity.date_format' => $this->localizedMessage('validation.custom.validity.date_format', 'A validade deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'contract_end_date.date' => $this->localizedMessage('validation.custom.contract_end_date.date', 'A data de fim do contrato deve ser válida.'),
|
||||||
|
'contract_end_date.date_format' => $this->localizedMessage('validation.custom.contract_end_date.date_format', 'A data de fim de contrato deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'contract_date.date' => $this->localizedMessage('validation.custom.contract_date.date', 'A data do contrato deve ser válida.'),
|
||||||
|
'contract_date.date_format' => $this->localizedMessage('validation.custom.contract_date.date_format', 'A data do contrato deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'activation_date.date' => $this->localizedMessage('validation.custom.activation_date.date', 'A data de ativação deve ser válida.'),
|
||||||
|
'activation_date.date_format' => $this->localizedMessage('validation.custom.activation_date.date_format', 'A data de ativação deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'status_id.required' => $this->localizedMessage('validation.custom.status_id.required', 'O estado do contrato é obrigatório.'),
|
||||||
|
'status_id.exists' => $this->localizedMessage('validation.custom.status_id.exists', 'O estado do contrato selecionado não é válido.'),
|
||||||
|
'commission_rate.numeric' => $this->localizedMessage('validation.custom.commission_rate.numeric', 'A percentagem da comissão deve ser um número.'),
|
||||||
|
'commission_rate.between' => $this->localizedMessage('validation.custom.commission_rate.between', 'A percentagem da comissão deve estar entre 0 e 100.'),
|
||||||
|
'notes.string' => $this->localizedMessage('validation.custom.notes.string', 'As notas devem ser texto.'),
|
||||||
|
'power_tier_id.exists' => $this->localizedMessage('validation.custom.power_tier_id.exists', 'O escalão de potência selecionado não é válido.'),
|
||||||
|
'payment_method_id.required' => $this->localizedMessage('validation.custom.payment_method_id.required', 'O método de pagamento é obrigatório.'),
|
||||||
|
'payment_method_id.exists' => $this->localizedMessage('validation.custom.payment_method_id.exists', 'O método de pagamento selecionado não é válido.'),
|
||||||
|
'delivery_method.required' => $this->localizedMessage('validation.custom.delivery_method.required', 'O método de envio é obrigatório.'),
|
||||||
|
'delivery_method.in' => $this->localizedMessage('validation.custom.delivery_method.in', 'O método de envio deve ser "correio" ou "email".'),
|
||||||
|
'delivery_email.email' => $this->localizedMessage('validation.custom.delivery_email.email', 'O e-mail de envio deve ser válido.'),
|
||||||
|
'delivery_email.max' => $this->localizedMessage('validation.custom.delivery_email.max', 'O e-mail de envio não pode exceder 255 caracteres.'),
|
||||||
|
'previous_supplier_id.exists' => $this->localizedMessage('validation.custom.previous_supplier_id.exists', 'O fornecedor anterior selecionado não é válido.'),
|
||||||
|
'new_supplier_id.exists' => $this->localizedMessage('validation.custom.new_supplier_id.exists', 'O novo fornecedor selecionado não é válido.'),
|
||||||
|
'paid.required' => $this->localizedMessage('validation.custom.paid.required', 'O campo "Pago" é obrigatório.'),
|
||||||
|
'paid.boolean' => $this->localizedMessage('validation.custom.paid.boolean', 'O campo "Pago" deve ser verdadeiro ou falso.'),
|
||||||
|
'chargeback.boolean' => $this->localizedMessage('validation.custom.chargeback.boolean', 'O campo "Chargeback" deve ser verdadeiro ou falso.'),
|
||||||
|
'chargeback_value.required_if' => $this->localizedMessage('validation.custom.chargeback_value.required_if', 'O valor do chargeback é obrigatório.'),
|
||||||
|
'chargeback_value.numeric' => $this->localizedMessage('validation.custom.chargeback_value.numeric', 'O valor do chargeback deve ser numérico.'),
|
||||||
|
'chargeback_value.max' => $this->localizedMessage('validation.custom.chargeback_value.max', 'O valor do chargeback deve ser negativo.'),
|
||||||
|
'chargeback_date.required_if' => $this->localizedMessage('validation.custom.chargeback_date.required_if', 'A data de chargeback é obrigatória.'),
|
||||||
|
'chargeback_date.date' => $this->localizedMessage('validation.custom.chargeback_date.date', 'A data de chargeback deve ser uma data válida.'),
|
||||||
|
'chargeback_date.date_format' => $this->localizedMessage('validation.custom.chargeback_date.date_format', 'A data de chargeback deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'installments.*.due_date.required_with' => $this->localizedMessage('validation.custom.installments_due_date.required_with', 'A data da parcela é obrigatória quando o valor está preenchido.'),
|
||||||
|
'installments.*.due_date.date' => $this->localizedMessage('validation.custom.installments_due_date.date', 'A data da parcela deve ser uma data válida.'),
|
||||||
|
'installments.*.due_date.date_format' => $this->localizedMessage('validation.custom.installments_due_date.date_format', 'A data da parcela deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'installments.*.amount.required_with' => $this->localizedMessage('validation.custom.installments_amount.required_with', 'O valor da parcela é obrigatório quando a data está preenchida.'),
|
||||||
|
'installments.*.amount.numeric' => $this->localizedMessage('validation.custom.installments_amount.numeric', 'O valor da parcela deve ser numérico.'),
|
||||||
|
'installments.*.amount.min' => $this->localizedMessage('validation.custom.installments_amount.min', 'O valor da parcela não pode ser negativo.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
81
app/Http/Requests/CreateClientRequest.php
Normal file
81
app/Http/Requests/CreateClientRequest.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class CreateClientRequest extends FormRequest
|
||||||
|
{
|
||||||
|
protected function localizedMessage(string $key, string $fallback): string
|
||||||
|
{
|
||||||
|
if (function_exists('app') && app()->bound('translator')) {
|
||||||
|
return __($key, [], app()->getLocale()) ?: $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'tax_number' => 'required|digits:9|unique:clients,tax_number',
|
||||||
|
'email' => 'required|email',
|
||||||
|
'phone' => 'required|string|max:20',
|
||||||
|
'address' => 'required|string|max:255',
|
||||||
|
'contact_info' => 'nullable|string|max:255',
|
||||||
|
'notes' => 'nullable|string|max:500',
|
||||||
|
'client_type_id' => 'required|exists:client_types,id',
|
||||||
|
'seller_id' => 'required|exists:users,id',
|
||||||
|
'locality' => 'nullable|string|max:255'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom error messages for validation rules.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name.required' => $this->localizedMessage('validation.custom.name.required', 'O nome é obrigatório.'),
|
||||||
|
'name.string' => $this->localizedMessage('validation.custom.name.string', 'O nome deve ser texto.'),
|
||||||
|
'name.max' => $this->localizedMessage('validation.custom.name.max', 'O nome não pode ter mais de 255 caracteres.'),
|
||||||
|
'tax_number.required' => $this->localizedMessage('validation.custom.tax_number.required', 'O NIF é obrigatório.'),
|
||||||
|
'tax_number.digits' => $this->localizedMessage('validation.custom.tax_number.digits', 'O NIF deve ter 9 dígitos.'),
|
||||||
|
'tax_number.unique' => $this->localizedMessage('validation.custom.tax_number.unique', 'Já existe um cliente com este NIF.'),
|
||||||
|
'email.required' => $this->localizedMessage('validation.custom.email.required', 'O e-mail é obrigatório.'),
|
||||||
|
'email.email' => $this->localizedMessage('validation.custom.email.email', 'O e-mail deve ser válido.'),
|
||||||
|
'phone.required' => $this->localizedMessage('validation.custom.phone.required', 'O telefone é obrigatório.'),
|
||||||
|
'phone.string' => $this->localizedMessage('validation.custom.phone.string', 'O telefone deve ser texto.'),
|
||||||
|
'phone.max' => $this->localizedMessage('validation.custom.phone.max', 'O telefone não pode ter mais de 20 caracteres.'),
|
||||||
|
'address.required' => $this->localizedMessage('validation.custom.address.required', 'A morada é obrigatória.'),
|
||||||
|
'address.string' => $this->localizedMessage('validation.custom.address.string', 'A morada deve ser texto.'),
|
||||||
|
'address.max' => $this->localizedMessage('validation.custom.address.max', 'A morada não pode ter mais de 255 caracteres.'),
|
||||||
|
'contact_info.string' => $this->localizedMessage('validation.custom.contact_info.string', 'As informações de contacto devem ser texto.'),
|
||||||
|
'contact_info.max' => $this->localizedMessage('validation.custom.contact_info.max', 'As informações de contacto não podem exceder 255 caracteres.'),
|
||||||
|
'notes.string' => $this->localizedMessage('validation.custom.notes.string', 'As notas devem ser texto.'),
|
||||||
|
'notes.max' => $this->localizedMessage('validation.custom.notes.max', 'As notas não podem exceder 500 caracteres.'),
|
||||||
|
'client_type_id.required' => $this->localizedMessage('validation.custom.client_type_id.required', 'O tipo de cliente é obrigatório.'),
|
||||||
|
'client_type_id.exists' => $this->localizedMessage('validation.custom.client_type_id.exists', 'O tipo de cliente selecionado não é válido.'),
|
||||||
|
'seller_id.required' => $this->localizedMessage('validation.custom.seller_id.required', 'O vendedor é obrigatório.'),
|
||||||
|
'seller_id.exists' => $this->localizedMessage('validation.custom.seller_id.exists', 'O vendedor selecionado não é válido.'),
|
||||||
|
'locality.string' => $this->localizedMessage('validation.custom.locality.string', 'A localidade deve ser texto.'),
|
||||||
|
'locality.max' => $this->localizedMessage('validation.custom.locality.max', 'A localidade não pode ter mais de 255 caracteres.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
72
app/Http/Requests/LeadRequest.php
Normal file
72
app/Http/Requests/LeadRequest.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class LeadRequest extends FormRequest
|
||||||
|
{
|
||||||
|
protected function localizedMessage(string $key, string $fallback): string
|
||||||
|
{
|
||||||
|
if (function_exists('app') && app()->bound('translator')) {
|
||||||
|
return __($key, [], app()->getLocale()) ?: $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'nullable|email|max:255',
|
||||||
|
'phone' => 'nullable|string|max:20',
|
||||||
|
'lead_status_id' => 'nullable|exists:lead_status,id',
|
||||||
|
'notes' => 'nullable|string',
|
||||||
|
'contact_info' => 'nullable|string',
|
||||||
|
'address' => 'nullable|string|max:255',
|
||||||
|
'contact_date' => 'nullable|date_format:d/m/Y',
|
||||||
|
'contract_end_date' => 'nullable|date_format:d/m/Y|after_or_equal:contact_date',
|
||||||
|
'contract_type' => 'nullable|in:CPE,CUI',
|
||||||
|
'delivery_point_code' => 'nullable'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name.required' => $this->localizedMessage('validation.custom.name.required', 'O nome é obrigatório.'),
|
||||||
|
'name.string' => $this->localizedMessage('validation.custom.name.string', 'O nome deve ser texto.'),
|
||||||
|
'name.max' => $this->localizedMessage('validation.custom.name.max', 'O nome não pode ter mais de 255 caracteres.'),
|
||||||
|
'email.email' => $this->localizedMessage('validation.custom.email.email', 'O e-mail deve ser válido.'),
|
||||||
|
'email.max' => $this->localizedMessage('validation.custom.email.max', 'O e-mail não pode ter mais de 255 caracteres.'),
|
||||||
|
'phone.string' => $this->localizedMessage('validation.custom.phone.string', 'O telefone deve ser texto.'),
|
||||||
|
'phone.max' => $this->localizedMessage('validation.custom.phone.max', 'O telefone não pode ter mais de 20 caracteres.'),
|
||||||
|
'lead_status_id.exists' => $this->localizedMessage('validation.custom.lead_status_id.exists', 'O estado do lead selecionado não é válido.'),
|
||||||
|
'notes.string' => $this->localizedMessage('validation.custom.notes.string', 'As notas devem ser texto.'),
|
||||||
|
'contact_info.string' => $this->localizedMessage('validation.custom.contact_info.string', 'As informações de contacto devem ser texto.'),
|
||||||
|
'contact_info.max' => $this->localizedMessage('validation.custom.contact_info.max', 'As informações de contacto não podem exceder 255 caracteres.'),
|
||||||
|
'address.string' => $this->localizedMessage('validation.custom.address.string', 'A morada deve ser texto.'),
|
||||||
|
'address.max' => $this->localizedMessage('validation.custom.address.max', 'A morada não pode ter mais de 255 caracteres.'),
|
||||||
|
'contact_date.date' => $this->localizedMessage('validation.custom.contact_date.date', 'A data de contacto deve ser válida.'),
|
||||||
|
'contact_date.date_format' => $this->localizedMessage('validation.custom.contact_date.date_format', 'A data de contacto deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'contract_end_date.date' => $this->localizedMessage('validation.custom.contract_end_date.date', 'A data de fim do contrato deve ser válida.'),
|
||||||
|
'contract_end_date.after_or_equal' => $this->localizedMessage('validation.custom.contract_end_date.after_or_equal', 'A data de fim de contrato deve ser igual ou posterior à data de contacto.'),
|
||||||
|
'contract_end_date.date_format' => $this->localizedMessage('validation.custom.contract_end_date.date_format', 'A data de fim de contrato deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'contract_type.in' => $this->localizedMessage('validation.custom.contract_type.in', 'O tipo de contrato deve ser CPE ou CUI.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
83
app/Http/Requests/RenewContractRequest.php
Normal file
83
app/Http/Requests/RenewContractRequest.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class RenewContractRequest extends FormRequest
|
||||||
|
{
|
||||||
|
protected function localizedMessage(string $key, string $fallback): string
|
||||||
|
{
|
||||||
|
if (function_exists('app') && app()->bound('translator')) {
|
||||||
|
return __($key, [], app()->getLocale()) ?: $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
foreach (['next_due_date', 'contract_date', 'activation_date'] as $field) {
|
||||||
|
$value = $this->input($field);
|
||||||
|
|
||||||
|
if ($value && str_contains($value, '/')) {
|
||||||
|
try {
|
||||||
|
$this->merge([
|
||||||
|
$field => Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d'),
|
||||||
|
]);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// Mantém o valor original para o validator apresentar a mensagem adequada.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'next_due_date' => ['required', 'date_format:Y-m-d'],
|
||||||
|
'annual_consumption' => ['nullable', 'numeric'],
|
||||||
|
'contract_type_id' => ['required', 'exists:contract_types,id'],
|
||||||
|
'typology_id' => ['nullable', 'exists:typologies,id'],
|
||||||
|
'contract_date' => ['nullable', 'date_format:Y-m-d'],
|
||||||
|
'activation_date' => ['nullable', 'date_format:Y-m-d'],
|
||||||
|
'renewal_type' => ['required', Rule::exists('renewal_types', 'value')],
|
||||||
|
'new_supplier_id' => ['required_if:change_supplier,1', 'nullable', 'exists:suppliers,id'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->user()?->can('view_contract_summary')) {
|
||||||
|
$rules['amount_paid'] = ['required', 'numeric'];
|
||||||
|
$rules['commission_value'] = ['nullable', 'numeric'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'amount_paid.required' => $this->localizedMessage('validation.custom.amount_paid.required', 'O valor do contrato é obrigatório.'),
|
||||||
|
'amount_paid.numeric' => $this->localizedMessage('validation.custom.amount_paid.numeric', 'O valor do contrato deve ser um número.'),
|
||||||
|
'commission_value.numeric' => $this->localizedMessage('validation.custom.commission_value.numeric', 'A comissão deve ser um número.'),
|
||||||
|
'next_due_date.required' => $this->localizedMessage('validation.custom.next_due_date.required', 'A próxima renovação é obrigatória.'),
|
||||||
|
'next_due_date.date_format' => $this->localizedMessage('validation.custom.next_due_date.date_format', 'A próxima renovação deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'renewal_type.required' => $this->localizedMessage('validation.custom.renewal_type.required', 'O tipo de renovação é obrigatório.'),
|
||||||
|
'renewal_type.exists' => $this->localizedMessage('validation.custom.renewal_type.exists', 'O tipo de renovação selecionado não é válido.'),
|
||||||
|
'annual_consumption.numeric' => $this->localizedMessage('validation.custom.annual_consumption.numeric', 'O consumo anual deve ser numérico.'),
|
||||||
|
'contract_type_id.required' => $this->localizedMessage('validation.custom.contract_type_id.required', 'O tipo de contrato é obrigatório.'),
|
||||||
|
'contract_type_id.exists' => $this->localizedMessage('validation.custom.contract_type_id.exists', 'O tipo de contrato selecionado não é válido.'),
|
||||||
|
'typology_id.exists' => $this->localizedMessage('validation.custom.typology_id.exists', 'A tipologia selecionada não é válida.'),
|
||||||
|
'contract_date.date_format' => $this->localizedMessage('validation.custom.contract_date.date_format', 'A data do contrato deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'activation_date.date_format' => $this->localizedMessage('validation.custom.activation_date.date_format', 'A data de ativação deve estar no formato DD/MM/AAAA.'),
|
||||||
|
'new_supplier_id.required_if' => $this->localizedMessage('validation.custom.new_supplier_id.required_if', 'O novo fornecedor é obrigatório quando a alteração está ativa.'),
|
||||||
|
'new_supplier_id.exists' => $this->localizedMessage('validation.custom.new_supplier_id.exists', 'O novo fornecedor selecionado não é válido.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
83
app/Http/Requests/UpdateClientRequest.php
Normal file
83
app/Http/Requests/UpdateClientRequest.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdateClientRequest extends FormRequest
|
||||||
|
{
|
||||||
|
protected function localizedMessage(string $key, string $fallback): string
|
||||||
|
{
|
||||||
|
if (function_exists('app') && app()->bound('translator')) {
|
||||||
|
return __($key, [], app()->getLocale()) ?: $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'tax_number' => 'required|digits:9|unique:clients,tax_number,' . $this->route('id'),
|
||||||
|
'email' => 'required|email',
|
||||||
|
'phone' => 'required|string|max:20',
|
||||||
|
'address' => 'required|string|max:255',
|
||||||
|
'contact_info' => 'nullable|string|max:255',
|
||||||
|
'notes' => 'nullable|string|max:500',
|
||||||
|
'seller_id' => 'required|exists:users,id',
|
||||||
|
'client_type_id' => 'required|exists:client_types,id',
|
||||||
|
'locality' => 'nullable|string|max:255'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom error messages for validation rules.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name.required' => $this->localizedMessage('validation.custom.name.required', 'O nome é obrigatório.'),
|
||||||
|
'name.string' => $this->localizedMessage('validation.custom.name.string', 'O nome deve ser texto.'),
|
||||||
|
'name.max' => $this->localizedMessage('validation.custom.name.max', 'O nome não pode ter mais de 255 caracteres.'),
|
||||||
|
'tax_number.required' => $this->localizedMessage('validation.custom.tax_number.required', 'O NIF é obrigatório.'),
|
||||||
|
'tax_number.digits' => $this->localizedMessage('validation.custom.tax_number.digits', 'O NIF deve ter 9 dígitos.'),
|
||||||
|
'tax_number.unique' => $this->localizedMessage('validation.custom.tax_number.unique', 'Já existe um cliente com este NIF.'),
|
||||||
|
'email.required' => $this->localizedMessage('validation.custom.email.required', 'O e-mail é obrigatório.'),
|
||||||
|
'email.email' => $this->localizedMessage('validation.custom.email.email', 'O e-mail deve ser válido.'),
|
||||||
|
'phone.required' => $this->localizedMessage('validation.custom.phone.required', 'O telefone é obrigatório.'),
|
||||||
|
'phone.string' => $this->localizedMessage('validation.custom.phone.string', 'O telefone deve ser texto.'),
|
||||||
|
'phone.max' => $this->localizedMessage('validation.custom.phone.max', 'O telefone não pode ter mais de 20 caracteres.'),
|
||||||
|
'address.required' => $this->localizedMessage('validation.custom.address.required', 'A morada é obrigatória.'),
|
||||||
|
'address.string' => $this->localizedMessage('validation.custom.address.string', 'A morada deve ser texto.'),
|
||||||
|
'address.max' => $this->localizedMessage('validation.custom.address.max', 'A morada não pode ter mais de 255 caracteres.'),
|
||||||
|
'contact_info.string' => $this->localizedMessage('validation.custom.contact_info.string', 'As informações de contacto devem ser texto.'),
|
||||||
|
'contact_info.max' => $this->localizedMessage('validation.custom.contact_info.max', 'As informações de contacto não podem exceder 255 caracteres.'),
|
||||||
|
'notes.string' => $this->localizedMessage('validation.custom.notes.string', 'As notas devem ser texto.'),
|
||||||
|
'notes.max' => $this->localizedMessage('validation.custom.notes.max', 'As notas não podem exceder 500 caracteres.'),
|
||||||
|
'seller_id.required' => $this->localizedMessage('validation.custom.seller_id.required', 'O vendedor é obrigatório.'),
|
||||||
|
'seller_id.exists' => $this->localizedMessage('validation.custom.seller_id.exists', 'O vendedor selecionado não é válido.'),
|
||||||
|
'client_type_id.required' => $this->localizedMessage('validation.custom.client_type_id.required', 'O tipo de cliente é obrigatório.'),
|
||||||
|
'client_type_id.exists' => $this->localizedMessage('validation.custom.client_type_id.exists', 'O tipo de cliente selecionado não é válido.'),
|
||||||
|
'locality.string' => $this->localizedMessage('validation.custom.locality.string', 'A localidade deve ser texto.'),
|
||||||
|
'locality.max' => $this->localizedMessage('validation.custom.locality.max', 'A localidade não pode ter mais de 255 caracteres.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Mail/NewUserWelcome.php
Normal file
28
app/Mail/NewUserWelcome.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class NewUserWelcome extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $user;
|
||||||
|
public $password;
|
||||||
|
|
||||||
|
public function __construct(User $user, string $password)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
$this->password = $password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build()
|
||||||
|
{
|
||||||
|
return $this->subject('Conta criada com sucesso')
|
||||||
|
->view('emails.new_user_welcome');
|
||||||
|
}
|
||||||
|
}
|
||||||
87
app/Models/CPE.php
Normal file
87
app/Models/CPE.php
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CPE extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'cpe';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'client_id',
|
||||||
|
'pvp',
|
||||||
|
'net',
|
||||||
|
'power',
|
||||||
|
'tier',
|
||||||
|
'annual_consumption',
|
||||||
|
'contract_type_id',
|
||||||
|
'validity',
|
||||||
|
'activation_date',
|
||||||
|
'renewal_date',
|
||||||
|
'notes',
|
||||||
|
'payment_date',
|
||||||
|
'previous_supplier',
|
||||||
|
'status_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $dates = [
|
||||||
|
'validity',
|
||||||
|
'activation_date',
|
||||||
|
'renewal_date',
|
||||||
|
'payment_date',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'id'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function client()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Clients::class, 'client_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contractType()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ContractType::class, 'contract_type_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function status()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ContractStatus::class, 'status_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function payments()
|
||||||
|
{
|
||||||
|
return $this->hasMany(CpePayment::class, 'cpe_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function totalPaid()
|
||||||
|
{
|
||||||
|
return $this->payments()->where('status', 'paid')->sum('amount');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPaid()
|
||||||
|
{
|
||||||
|
return $this->totalPaid() >= $this->pvp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCurrentRenewalPaid()
|
||||||
|
{
|
||||||
|
return $this->payments()
|
||||||
|
->where('reference_period', $this->renewal_date?->format('Y-m-d'))
|
||||||
|
->where('status', 'paid')
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
15
app/Models/ClientType.php
Normal file
15
app/Models/ClientType.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ClientType extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = ['description', 'icon', 'color'];
|
||||||
|
|
||||||
|
public function clients()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Clients::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
app/Models/Clients.php
Normal file
71
app/Models/Clients.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class Clients extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'tax_number',
|
||||||
|
'address',
|
||||||
|
'phone',
|
||||||
|
'email',
|
||||||
|
'seller_id',
|
||||||
|
'receive_invoice_id',
|
||||||
|
'client_type_id',
|
||||||
|
'contact_info',
|
||||||
|
'notes',
|
||||||
|
'type',
|
||||||
|
'lead_status_id',
|
||||||
|
'locality'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function contracts()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Contract::class, 'client_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function seller(): BelongsTo {
|
||||||
|
return $this->belongsTo(User::class, 'seller_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cpes()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Cpe::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasUnpaidCpes()
|
||||||
|
{
|
||||||
|
return $this->cpes->contains(function ($cpe) {
|
||||||
|
return !$cpe->isPaid();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clientType()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ClientType::class, 'client_type_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function leadStatus()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(LeadStatus::class, 'lead_status_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function leadDetail()
|
||||||
|
{
|
||||||
|
return $this->hasOne(LeadDetail::class, 'client_id', 'id');
|
||||||
|
}
|
||||||
|
}
|
||||||
224
app/Models/Contract.php
Normal file
224
app/Models/Contract.php
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class Contract extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'client_id',
|
||||||
|
'paid',
|
||||||
|
'chargeback',
|
||||||
|
'chargeback_value',
|
||||||
|
'chargeback_date',
|
||||||
|
'contract_value',
|
||||||
|
'commission_value',
|
||||||
|
'profit_value',
|
||||||
|
'seller_id',
|
||||||
|
'supply_type',
|
||||||
|
'supply_address',
|
||||||
|
'annual_consumption',
|
||||||
|
'code',
|
||||||
|
'contract_type_id',
|
||||||
|
'status_id',
|
||||||
|
'commission_rate',
|
||||||
|
'notes',
|
||||||
|
'power_tier_id',
|
||||||
|
'payment_method_id',
|
||||||
|
'delivery_method',
|
||||||
|
'delivery_email',
|
||||||
|
'previous_supplier_id',
|
||||||
|
'new_supplier_id',
|
||||||
|
'typology_id',
|
||||||
|
'cui_tier_id',
|
||||||
|
'activation_date',
|
||||||
|
'contract_date',
|
||||||
|
'validity',
|
||||||
|
'contract_end_date',
|
||||||
|
'go_ki'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function histories()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ContractHistory::class)->orderBy('created_at', 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function client(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Clients::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contractType(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ContractType::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function status(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ContractStatus::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function payments(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Payment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function powerTier(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(PowerTier::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cuiTier(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CuiTier::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paymentMethod(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(PaymentMethod::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function previousSupplier(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Supplier::class, 'previous_supplier_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newSupplier(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Supplier::class, 'new_supplier_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function typology()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Typology::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function installments()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ContractInstallment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gerar parcelas automaticamente
|
||||||
|
public function createInstallments()
|
||||||
|
{
|
||||||
|
$amount = $this->contract_value / $this->installment_count;
|
||||||
|
for ($i = 1; $i <= $this->installment_count; $i++) {
|
||||||
|
$this->installments()->create([
|
||||||
|
'installment_number' => $i,
|
||||||
|
'amount' => $amount,
|
||||||
|
'due_date' => now()->addMonths($i - 1),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Scopes
|
||||||
|
public function scopeCpe($query)
|
||||||
|
{
|
||||||
|
return $query->where('type', 'cpe');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeCpi($query)
|
||||||
|
{
|
||||||
|
return $query->where('type', 'cpi');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function seller()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'seller_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeBetweenDates($query, $from, $to)
|
||||||
|
{
|
||||||
|
return $query->when($from, fn($q) => $q->where('contract_date', '>=', $from))
|
||||||
|
->when($to, fn($q) => $q->where('contract_date', '<=', $to));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMonthlyGrowth($from = null, $to = null)
|
||||||
|
{
|
||||||
|
$driver = \Illuminate\Support\Facades\DB::getDriverName();
|
||||||
|
$monthExpression = $driver === 'sqlite'
|
||||||
|
? "strftime('%Y-%m', contract_date)"
|
||||||
|
: "DATE_FORMAT(contract_date, '%Y-%m')";
|
||||||
|
|
||||||
|
return self::query()
|
||||||
|
->betweenDates($from, $to)
|
||||||
|
->selectRaw($monthExpression . ' as mes, SUM(contract_value) as total')
|
||||||
|
->groupByRaw($monthExpression)
|
||||||
|
->orderByRaw($monthExpression)
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutators & Accessors
|
||||||
|
protected function activationDate(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
// Getter: returns a Carbon instance or null
|
||||||
|
get: fn ($value) => $value ? Carbon::parse($value) : null,
|
||||||
|
|
||||||
|
// Setter: accepts 'd/m/Y' from form, converts to 'Y-m-d' for DB
|
||||||
|
set: fn ($value) => $this->convertDateToDbFormat($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function contractDate(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
// Getter: returns a Carbon instance or null
|
||||||
|
get: fn ($value) => $value ? Carbon::parse($value) : null,
|
||||||
|
|
||||||
|
// Setter: accepts 'd/m/Y' from form, converts to 'Y-m-d' for DB
|
||||||
|
set: fn ($value) => $this->convertDateToDbFormat($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validity(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
// Getter: returns a Carbon instance or null
|
||||||
|
get: fn ($value) => $value ? Carbon::parse($value) : null,
|
||||||
|
|
||||||
|
// Setter: accepts 'd/m/Y' from form, converts to 'Y-m-d' for DB
|
||||||
|
set: fn ($value) => $this->convertDateToDbFormat($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function contractEndDate(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn ($value) => $value ? Carbon::parse($value) : null,
|
||||||
|
set: fn ($value) => $this->convertDateToDbFormat($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a date string from 'd/m/Y' or other formats to 'Y-m-d' for DB storage.
|
||||||
|
*
|
||||||
|
* @param string|null $value
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
private function convertDateToDbFormat($value)
|
||||||
|
{
|
||||||
|
$value = trim($value);
|
||||||
|
if (!$value) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try parsing d/m/Y format
|
||||||
|
return Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
try {
|
||||||
|
// Fallback: parse standard date formats (e.g., Y-m-d)
|
||||||
|
return Carbon::parse($value)->format('Y-m-d');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// If parsing fails, store null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
116
app/Models/ContractHistory.php
Normal file
116
app/Models/ContractHistory.php
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
|
class ContractHistory extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'contract_histories';
|
||||||
|
|
||||||
|
// Campos que podem ser preenchidos em massa
|
||||||
|
protected $fillable = [
|
||||||
|
'contract_id',
|
||||||
|
'previous_supplier_id',
|
||||||
|
'current_supplier_id',
|
||||||
|
'annual_consumption',
|
||||||
|
'seller_id',
|
||||||
|
'contract_type_id',
|
||||||
|
'typology_id',
|
||||||
|
'contract_date',
|
||||||
|
'activation_date',
|
||||||
|
'renewal_date',
|
||||||
|
'renewal_type',
|
||||||
|
'go_ki',
|
||||||
|
'validity',
|
||||||
|
'contract_value',
|
||||||
|
'commission_value',
|
||||||
|
'profit_value',
|
||||||
|
'chargeback',
|
||||||
|
'chargeback_value',
|
||||||
|
'chargeback_date',
|
||||||
|
'profit_margin',
|
||||||
|
'total_installments',
|
||||||
|
'paid_installments',
|
||||||
|
'notes',
|
||||||
|
'contract_snapshot'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function contract()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Contract::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function previousSupplier()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Supplier::class, 'previous_supplier_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function currentSupplier()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Supplier::class, 'current_supplier_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function seller()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'seller_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contractType()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ContractType::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected function validity(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
// Getter: returns a Carbon instance or null
|
||||||
|
get: fn ($value) => $value ? Carbon::parse($value) : null,
|
||||||
|
|
||||||
|
// Setter: accepts 'd/m/Y' from form, converts to 'Y-m-d' for DB
|
||||||
|
set: fn ($value) => $this->convertDateToDbFormat($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function chargebackDate(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn ($value) => $value ? Carbon::parse($value) : null,
|
||||||
|
set: fn ($value) => $this->convertDateToDbFormat($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a date string from 'd/m/Y' or other formats to 'Y-m-d' for DB storage.
|
||||||
|
*
|
||||||
|
* @param string|null $value
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
private function convertDateToDbFormat($value)
|
||||||
|
{
|
||||||
|
$value = trim($value);
|
||||||
|
if (!$value) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try parsing d/m/Y format
|
||||||
|
return Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
try {
|
||||||
|
// Fallback: parse standard date formats (e.g., Y-m-d)
|
||||||
|
return Carbon::parse($value)->format('Y-m-d');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// If parsing fails, store null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Models/ContractInstallment.php
Normal file
22
app/Models/ContractInstallment.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ContractInstallment extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'contract_id',
|
||||||
|
'installment_number',
|
||||||
|
'amount',
|
||||||
|
'due_date',
|
||||||
|
'paid',
|
||||||
|
'paid_at'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function contract()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Contract::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Models/ContractStatus.php
Normal file
25
app/Models/ContractStatus.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ContractStatus extends Model
|
||||||
|
{
|
||||||
|
protected $table = "contract_status";
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'description',
|
||||||
|
'code',
|
||||||
|
'color'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'id'
|
||||||
|
];
|
||||||
|
}
|
||||||
25
app/Models/ContractType.php
Normal file
25
app/Models/ContractType.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ContractType extends Model
|
||||||
|
{
|
||||||
|
protected $table = "contract_types";
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'code',
|
||||||
|
'description',
|
||||||
|
'status'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'id'
|
||||||
|
];
|
||||||
|
}
|
||||||
21
app/Models/CpePayment.php
Normal file
21
app/Models/CpePayment.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CpePayment extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'cpe_id',
|
||||||
|
'amount',
|
||||||
|
'reference_period',
|
||||||
|
'payment_date',
|
||||||
|
'status',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function cpe()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CPE::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
app/Models/CuiTier.php
Normal file
13
app/Models/CuiTier.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CuiTier extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['name', 'status'];
|
||||||
|
}
|
||||||
55
app/Models/LeadDetail.php
Normal file
55
app/Models/LeadDetail.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class LeadDetail extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'client_id',
|
||||||
|
'contact_date',
|
||||||
|
'contract_end_date',
|
||||||
|
'contract_type',
|
||||||
|
'delivery_point_code'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function client()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Clients::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accessor - quando for buscar do BD converte para d/m/Y
|
||||||
|
*/
|
||||||
|
public function getContactDateAttribute($value)
|
||||||
|
{
|
||||||
|
return $value ? Carbon::parse($value)->format('d/m/Y') : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContractEndDateAttribute($value)
|
||||||
|
{
|
||||||
|
return $value ? Carbon::parse($value)->format('d/m/Y') : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setContactDateAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['contact_date'] = $value
|
||||||
|
? Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d')
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContractEndDateAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['contract_end_date'] = $value
|
||||||
|
? Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d')
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
app/Models/LeadStatus.php
Normal file
20
app/Models/LeadStatus.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class LeadStatus extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'lead_status';
|
||||||
|
|
||||||
|
protected $fillable = ['name'];
|
||||||
|
|
||||||
|
public function clients()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Clients::class, 'lead_status_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/Models/Payment.php
Normal file
15
app/Models/Payment.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Payment extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = ['contract_id', 'amount', 'reference_period', 'payment_date', 'status'];
|
||||||
|
|
||||||
|
public function contract()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Contract::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/Models/PaymentMethod.php
Normal file
21
app/Models/PaymentMethod.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PaymentMethod extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'icon',
|
||||||
|
'status'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function cpe()
|
||||||
|
{
|
||||||
|
return $this->hasMany(cpe::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/Models/PowerTier.php
Normal file
10
app/Models/PowerTier.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PowerTier extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = ['name', 'status'];
|
||||||
|
}
|
||||||
12
app/Models/ReceiveInvoiceTypes.php
Normal file
12
app/Models/ReceiveInvoiceTypes.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ReceiveInvoiceTypes extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'description',
|
||||||
|
];
|
||||||
|
}
|
||||||
13
app/Models/RenewalType.php
Normal file
13
app/Models/RenewalType.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class RenewalType extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'value',
|
||||||
|
'description',
|
||||||
|
];
|
||||||
|
}
|
||||||
25
app/Models/Supplier.php
Normal file
25
app/Models/Supplier.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Supplier extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'status',
|
||||||
|
'color'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Opcional: se tiveres uma relação com contracts
|
||||||
|
public function previousContracts()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Contract::class, 'previous_supplier_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newContracts()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Contract::class, 'new_supplier_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/Models/Typology.php
Normal file
23
app/Models/Typology.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Typology extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'code',
|
||||||
|
'description',
|
||||||
|
'status'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'id'
|
||||||
|
];
|
||||||
|
}
|
||||||
55
app/Models/User.php
Normal file
55
app/Models/User.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
|
class User extends Authenticatable
|
||||||
|
{
|
||||||
|
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||||
|
use HasApiTokens, HasFactory, Notifiable, HasRoles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
'pin',
|
||||||
|
'can_login',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
'remember_token',
|
||||||
|
'pin',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email_verified_at' => 'datetime',
|
||||||
|
'password' => 'hashed',
|
||||||
|
'can_login' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/Providers/AppServiceProvider.php
Normal file
38
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Pagination\Paginator;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Maatwebsite\Excel\Excel;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Spatie\Permission\Middleware\RoleMiddleware;
|
||||||
|
|
||||||
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register any application services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
App::setLocale('pt');
|
||||||
|
Carbon::setLocale('pt');
|
||||||
|
Paginator::useBootstrap();
|
||||||
|
//URL::forceScheme('https');
|
||||||
|
|
||||||
|
Route::aliasMiddleware('role', RoleMiddleware::class);
|
||||||
|
Route::aliasMiddleware('permission', \Spatie\Permission\Middleware\PermissionMiddleware::class);
|
||||||
|
Route::aliasMiddleware('role_or_permission', \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Providers/RouteServiceProvider.php
Normal file
22
app/Providers/RouteServiceProvider.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||||
|
|
||||||
|
class RouteServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
$this->routes(function () {
|
||||||
|
|
||||||
|
Route::prefix('api')
|
||||||
|
->middleware('api')
|
||||||
|
->group(base_path('routes/api.php'));
|
||||||
|
|
||||||
|
Route::middleware('web')
|
||||||
|
->group(base_path('routes/web.php'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
53
app/Services/AdminService.php
Normal file
53
app/Services/AdminService.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class AdminService
|
||||||
|
{
|
||||||
|
public function info(): array
|
||||||
|
{
|
||||||
|
$databaseVersion = DB::connection()->getDriverName() === 'mysql'
|
||||||
|
? DB::select('SELECT VERSION() as version')[0]->version
|
||||||
|
: DB::connection()->getDriverName();
|
||||||
|
try {
|
||||||
|
$redisVersion = Redis::connection()->client()->info()['redis_version'] ?? 'N/A';
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
$redisVersion = 'N/A';
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'php_version' => PHP_VERSION, 'php_sapi' => PHP_SAPI, 'laravel_version' => app()->version(),
|
||||||
|
'environment' => app()->environment(), 'debug_mode' => config('app.debug'),
|
||||||
|
'database_driver' => config('database.default'), 'database_version' => $databaseVersion,
|
||||||
|
'redis_version' => $redisVersion, 'redis_database' => config('database.redis.cache.database'),
|
||||||
|
'cache_driver' => config('cache.default'), 'queue_driver' => config('queue.default'),
|
||||||
|
'session_driver' => config('session.driver'), 'memory_limit' => ini_get('memory_limit'), 'system' => php_uname(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function phpInfo(): string
|
||||||
|
{
|
||||||
|
ob_start();
|
||||||
|
phpinfo();
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runMigrations(): array
|
||||||
|
{
|
||||||
|
$exitCode = Artisan::call('migrate', ['--force' => true]);
|
||||||
|
return ['success' => $exitCode === 0, 'exit_code' => $exitCode, 'output' => trim(Artisan::output())];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmail(string $recipient): array
|
||||||
|
{
|
||||||
|
Mail::raw('Teste de envio de email.', function ($message) use ($recipient) {
|
||||||
|
$message->to($recipient)->subject('Teste de envio de email');
|
||||||
|
});
|
||||||
|
return ['success' => true, 'message' => 'Email de teste enviado com sucesso.'];
|
||||||
|
}
|
||||||
|
}
|
||||||
283
app/Services/AnalyticalService.php
Normal file
283
app/Services/AnalyticalService.php
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Contract;
|
||||||
|
use App\Models\ContractHistory;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class AnalyticalService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @version 2026/03
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$from = $this->parseDate($request->validity_from ?? $request->from ?? null);
|
||||||
|
$to = $this->parseDate($request->validity_to ?? $request->to ?? null);
|
||||||
|
|
||||||
|
$baseContractQuery = $this->applyFilters(Contract::query(), $request, 'contract_date');
|
||||||
|
$baseHistoryQuery = $this->applyFilters(ContractHistory::query(), $request, 'contract_date');
|
||||||
|
|
||||||
|
$contractTotals = (clone $baseContractQuery)
|
||||||
|
->selectRaw('COALESCE(SUM(contract_value), 0) as total_vendas, COALESCE(SUM(profit_value), 0) as total_lucro, COALESCE(SUM(commission_value), 0) as total_comissao, COUNT(*) as contagem, COUNT(DISTINCT client_id) as total_clients')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$historyTotals = (clone $baseHistoryQuery)
|
||||||
|
->selectRaw('COALESCE(SUM(contract_value), 0) as total_vendas, COALESCE(SUM(profit_value), 0) as total_lucro, COALESCE(SUM(commission_value), 0) as total_comissao, COUNT(*) as contagem, COUNT(DISTINCT contract_id) as total_clients')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$totalVendas = (float) ($contractTotals->total_vendas ?? 0) + (float) ($historyTotals->total_vendas ?? 0);
|
||||||
|
$totalLucro = (float) ($contractTotals->total_lucro ?? 0) + (float) ($historyTotals->total_lucro ?? 0);
|
||||||
|
$totalComissao = (float) ($contractTotals->total_comissao ?? 0) + (float) ($historyTotals->total_comissao ?? 0);
|
||||||
|
$contagem = (int) (($contractTotals->contagem ?? 0) + ($historyTotals->contagem ?? 0));
|
||||||
|
$totalClients = (int) max($contractTotals->total_clients ?? 0, $historyTotals->total_clients ?? 0);
|
||||||
|
|
||||||
|
$stats = [
|
||||||
|
'total_vendas' => $totalVendas,
|
||||||
|
'total_lucro' => $totalLucro,
|
||||||
|
'total_comissao' => $totalComissao,
|
||||||
|
'contagem' => $contagem,
|
||||||
|
'total_clients' => $totalClients,
|
||||||
|
];
|
||||||
|
|
||||||
|
$vendasMensais = $this->buildMonthlyGrowth($request);
|
||||||
|
|
||||||
|
$vendasPorTipo = $this->buildClientTypeBreakdown($request);
|
||||||
|
$totaisPorAno = $this->buildYearBreakdown($request);
|
||||||
|
$totaisPorComercializadora = $this->buildSupplierBreakdown($request);
|
||||||
|
|
||||||
|
return compact('stats', 'vendasMensais', 'vendasPorTipo', 'totaisPorAno', 'totaisPorComercializadora');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyFilters($query, Request $request, string $dateField = 'contract_date')
|
||||||
|
{
|
||||||
|
if (!str_contains($dateField, '.')) {
|
||||||
|
$dateField = $query->getModel()->getTable() . '.' . $dateField;
|
||||||
|
}
|
||||||
|
|
||||||
|
$from = $this->parseDate($request->validity_from ?? $request->from ?? null);
|
||||||
|
$to = $this->parseDate($request->validity_to ?? $request->to ?? null);
|
||||||
|
|
||||||
|
if ($from) {
|
||||||
|
$query->whereDate($dateField, '>=', $from);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($to) {
|
||||||
|
$query->whereDate($dateField, '<=', $to);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (['status_id', 'seller_id', 'supply_type', 'contract_type_id', 'year'] as $field) {
|
||||||
|
$value = $request->input($field);
|
||||||
|
if ($value === null || $value === '' || $value === 'all') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($field) {
|
||||||
|
case 'status_id':
|
||||||
|
$query->where('status_id', $value);
|
||||||
|
break;
|
||||||
|
case 'seller_id':
|
||||||
|
$query->where('seller_id', $value);
|
||||||
|
break;
|
||||||
|
case 'supply_type':
|
||||||
|
$query->where('supply_type', $value);
|
||||||
|
break;
|
||||||
|
case 'contract_type_id':
|
||||||
|
$query->where('contract_type_id', $value);
|
||||||
|
break;
|
||||||
|
case 'year':
|
||||||
|
$query->whereRaw($this->yearExpression($dateField) . ' = ?', [(int) $value]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('client_type_id')) {
|
||||||
|
$query->whereHas('client', fn ($q) => $q->where('client_type_id', $request->client_type_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('supplier_id')) {
|
||||||
|
$query->where(function ($q) use ($request) {
|
||||||
|
$q->where('new_supplier_id', $request->supplier_id)
|
||||||
|
->orWhere('previous_supplier_id', $request->supplier_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildMonthlyGrowth(Request $request)
|
||||||
|
{
|
||||||
|
$from = $this->parseDate($request->validity_from ?? $request->from ?? null);
|
||||||
|
$to = $this->parseDate($request->validity_to ?? $request->to ?? null);
|
||||||
|
|
||||||
|
$contractSeries = Contract::query();
|
||||||
|
$historySeries = ContractHistory::query();
|
||||||
|
|
||||||
|
$this->applyFilters($contractSeries, $request, 'contract_date');
|
||||||
|
$this->applyFilters($historySeries, $request, 'contract_date');
|
||||||
|
|
||||||
|
$monthExpression = $this->monthExpression('contract_date');
|
||||||
|
|
||||||
|
$contractTotals = (clone $contractSeries)
|
||||||
|
->selectRaw($monthExpression . ' as mes, SUM(contract_value) as total')
|
||||||
|
->whereNotNull('contract_date')
|
||||||
|
->groupByRaw($monthExpression)
|
||||||
|
->orderByRaw($monthExpression)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$historyTotals = (clone $historySeries)
|
||||||
|
->selectRaw($monthExpression . ' as mes, SUM(contract_value) as total')
|
||||||
|
->whereNotNull('contract_date')
|
||||||
|
->groupByRaw($monthExpression)
|
||||||
|
->orderByRaw($monthExpression)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$merged = [];
|
||||||
|
foreach ($contractTotals as $row) {
|
||||||
|
$merged[$row->mes] = (float) ($row->total ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($historyTotals as $row) {
|
||||||
|
$merged[$row->mes] = ($merged[$row->mes] ?? 0) + (float) ($row->total ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($merged);
|
||||||
|
|
||||||
|
return collect(array_map(fn ($month, $total) => ['mes' => $month, 'total' => $total], array_keys($merged), array_values($merged)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildClientTypeBreakdown(Request $request)
|
||||||
|
{
|
||||||
|
$contractBreakdown = (clone $this->applyFilters(Contract::query(), $request, 'contract_date'))
|
||||||
|
->join('clients', 'contracts.client_id', '=', 'clients.id')
|
||||||
|
->join('client_types', 'clients.client_type_id', '=', 'client_types.id')
|
||||||
|
->selectRaw('client_types.description as tipo, SUM(contracts.contract_value) as total')
|
||||||
|
->groupBy('client_types.description')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$historyBreakdown = (clone $this->applyFilters(ContractHistory::query(), $request, 'contract_date'))
|
||||||
|
->join('contracts', 'contract_histories.contract_id', '=', 'contracts.id')
|
||||||
|
->join('clients', 'contracts.client_id', '=', 'clients.id')
|
||||||
|
->join('client_types', 'clients.client_type_id', '=', 'client_types.id')
|
||||||
|
->selectRaw('client_types.description as tipo, SUM(contract_histories.contract_value) as total')
|
||||||
|
->groupBy('client_types.description')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$merged = [];
|
||||||
|
foreach ($contractBreakdown as $row) {
|
||||||
|
$merged[$row->tipo] = (float) ($row->total ?? 0);
|
||||||
|
}
|
||||||
|
foreach ($historyBreakdown as $row) {
|
||||||
|
$key = $row->tipo ?? 'Sem tipo';
|
||||||
|
$merged[$key] = ($merged[$key] ?? 0) + (float) ($row->total ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect(array_map(fn ($tipo, $total) => ['tipo' => $tipo, 'total' => $total], array_keys($merged), array_values($merged)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildYearBreakdown(Request $request)
|
||||||
|
{
|
||||||
|
$yearExpression = $this->yearExpression('contract_date');
|
||||||
|
|
||||||
|
$contractYears = (clone $this->applyFilters(Contract::query(), $request, 'contract_date'))
|
||||||
|
->selectRaw($yearExpression . ' as ano, SUM(contract_value) as total')
|
||||||
|
->whereNotNull('contract_date')
|
||||||
|
->groupByRaw($yearExpression)
|
||||||
|
->orderByRaw($yearExpression)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$historyYears = (clone $this->applyFilters(ContractHistory::query(), $request, 'contract_date'))
|
||||||
|
->selectRaw($yearExpression . ' as ano, SUM(contract_value) as total')
|
||||||
|
->whereNotNull('contract_date')
|
||||||
|
->groupByRaw($yearExpression)
|
||||||
|
->orderByRaw($yearExpression)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$merged = [];
|
||||||
|
foreach ($contractYears as $row) {
|
||||||
|
$merged[(string) $row->ano] = (float) ($row->total ?? 0);
|
||||||
|
}
|
||||||
|
foreach ($historyYears as $row) {
|
||||||
|
$key = (string) ($row->ano ?? '');
|
||||||
|
$merged[$key] = ($merged[$key] ?? 0) + (float) ($row->total ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($merged);
|
||||||
|
|
||||||
|
return collect(array_map(fn ($ano, $total) => ['ano' => $ano, 'total' => $total], array_keys($merged), array_values($merged)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildSupplierBreakdown(Request $request)
|
||||||
|
{
|
||||||
|
$contractSuppliers = (clone $this->applyFilters(Contract::query(), $request, 'contract_date'))
|
||||||
|
->join('suppliers', 'contracts.new_supplier_id', '=', 'suppliers.id')
|
||||||
|
->selectRaw('suppliers.name as fornecedor, SUM(contracts.contract_value) as total')
|
||||||
|
->groupBy('suppliers.name')
|
||||||
|
->orderBy('total', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$historySuppliers = (clone $this->applyFilters(ContractHistory::query(), $request, 'contract_date'))
|
||||||
|
->join('suppliers', 'contract_histories.current_supplier_id', '=', 'suppliers.id')
|
||||||
|
->selectRaw('suppliers.name as fornecedor, SUM(contract_histories.contract_value) as total')
|
||||||
|
->groupBy('suppliers.name')
|
||||||
|
->orderBy('total', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$merged = [];
|
||||||
|
foreach ($contractSuppliers as $row) {
|
||||||
|
$merged[$row->fornecedor] = (float) ($row->total ?? 0);
|
||||||
|
}
|
||||||
|
foreach ($historySuppliers as $row) {
|
||||||
|
$key = $row->fornecedor ?? 'Sem fornecedor';
|
||||||
|
$merged[$key] = ($merged[$key] ?? 0) + (float) ($row->total ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect(array_map(fn ($fornecedor, $total) => ['fornecedor' => $fornecedor, 'total' => $total], array_keys($merged), array_values($merged)))
|
||||||
|
->sortByDesc('total')->values();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function yearExpression(string $column): string
|
||||||
|
{
|
||||||
|
if (DB::getDriverName() === 'sqlite') {
|
||||||
|
return "CAST(strftime('%Y', {$column}) AS INTEGER)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "YEAR({$column})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function monthExpression(string $column): string
|
||||||
|
{
|
||||||
|
if (DB::getDriverName() === 'sqlite') {
|
||||||
|
return "strftime('%Y-%m', {$column})";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "DATE_FORMAT({$column}, '%Y-%m')";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseDate($date)
|
||||||
|
{
|
||||||
|
if ($date === null || $date === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$date = trim((string) $date);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
||||||
|
return Carbon::createFromFormat('Y-m-d', $date)->format('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $date)) {
|
||||||
|
return Carbon::createFromFormat('d/m/Y', $date)->format('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Carbon::parse($date)->format('Y-m-d');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
app/Services/AuthService.php
Normal file
73
app/Services/AuthService.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Events\PasswordReset;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Laravel\Sanctum\PersonalAccessToken;
|
||||||
|
|
||||||
|
class AuthService
|
||||||
|
{
|
||||||
|
public function authenticate(array $credentials): User
|
||||||
|
{
|
||||||
|
$user = User::where('email', $credentials['email'])->first();
|
||||||
|
abort_unless($user && Hash::check($credentials['password'], $user->password) && $user->can_login !== false, 401, 'Credenciais invalidas.');
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function issueToken(User $user): array
|
||||||
|
{
|
||||||
|
$token = $user->createToken('api-token', ['*'], now()->addMinutes(20));
|
||||||
|
return ['token' => $token->plainTextToken, 'user' => $user->only(['id', 'name', 'email'])];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sessionToken(User $user, string $plainText): ?PersonalAccessToken
|
||||||
|
{
|
||||||
|
$token = PersonalAccessToken::findToken($plainText);
|
||||||
|
if (!$token || $token->tokenable_type !== $user->getMorphClass() || (int) $token->tokenable_id !== (int) $user->id
|
||||||
|
|| ($token->expires_at && $token->expires_at->isPast()) || $user->can_login === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh(PersonalAccessToken $token): array
|
||||||
|
{
|
||||||
|
abort_if($token->expires_at && $token->expires_at->isPast(), 401, 'Token expirado.');
|
||||||
|
$token->update(['expires_at' => now()->addMinutes(20)]);
|
||||||
|
return [
|
||||||
|
'message' => 'Token renovado com sucesso',
|
||||||
|
'expires_at' => $token->expires_at->toISOString(),
|
||||||
|
'user' => $token->tokenable->only(['id', 'name', 'email']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function check(PersonalAccessToken $token): array
|
||||||
|
{
|
||||||
|
$valid = !$token->expires_at || $token->expires_at->isFuture();
|
||||||
|
$seconds = $valid && $token->expires_at ? max(0, now()->diffInSeconds($token->expires_at, false)) : 0;
|
||||||
|
return ['valid' => $valid, 'expires_at' => $token->expires_at?->toISOString(), 'expires_in' => $seconds, 'expires_in_minutes' => ceil($seconds / 60)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revoke(?PersonalAccessToken $token): void
|
||||||
|
{
|
||||||
|
$token?->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendResetLink(array $credentials): string
|
||||||
|
{
|
||||||
|
return Password::sendResetLink($credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetPassword(array $credentials): string
|
||||||
|
{
|
||||||
|
return Password::reset($credentials, function (User $user, string $password) {
|
||||||
|
$user->forceFill(['password' => Hash::make($password), 'remember_token' => Str::random(60)])->save();
|
||||||
|
$user->tokens()->delete();
|
||||||
|
event(new PasswordReset($user));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
95
app/Services/ClientService.php
Normal file
95
app/Services/ClientService.php
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Clients;
|
||||||
|
use App\Models\ClientType;
|
||||||
|
use App\Models\Contract;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Traits\ResourcesCache;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ClientService
|
||||||
|
{
|
||||||
|
use ResourcesCache;
|
||||||
|
|
||||||
|
public function listing(Request $request, bool $paginated = false)
|
||||||
|
{
|
||||||
|
$query = Clients::query()->where('status', 'A');
|
||||||
|
if ($paginated) {
|
||||||
|
$query->where('type', 'client');
|
||||||
|
}
|
||||||
|
foreach (['name', 'tax_number', 'email'] as $field) {
|
||||||
|
if ($request->filled($field)) {
|
||||||
|
$query->where($field, 'like', '%' . $request->input($field) . '%');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($request->filled('client_type_id')) {
|
||||||
|
$query->where('client_type_id', $request->input('client_type_id'));
|
||||||
|
}
|
||||||
|
if ($request->filled('contract_code')) {
|
||||||
|
$query->whereHas('contracts', fn ($contracts) => $contracts->where('code', 'like', '%' . $request->input('contract_code') . '%'));
|
||||||
|
}
|
||||||
|
return $paginated
|
||||||
|
? $query->orderByDesc('id')->paginate(max(1, min(100, (int) $request->input('per_page', 10))))->appends($request->query())
|
||||||
|
: $query->orderBy('id')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function types()
|
||||||
|
{
|
||||||
|
return ClientType::all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function form($type, User $user): array
|
||||||
|
{
|
||||||
|
return ['type' => ClientType::findOrFail($type), 'sellers' => $user->id === 1 ? User::all() : $this->_getSellers()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id)
|
||||||
|
{
|
||||||
|
return Clients::with('contracts')->findOrFail($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detail($id): array
|
||||||
|
{
|
||||||
|
$contracts = Contract::with(['contractType', 'status'])->where('client_id', $id)->orderByDesc('activation_date')->get();
|
||||||
|
return [
|
||||||
|
'client' => $this->find($id),
|
||||||
|
'contractsByStatus' => $contracts->groupBy(fn ($contract) => $contract->status->description ?? 'Sem status'),
|
||||||
|
'clientTypes' => $this->types(),
|
||||||
|
'sellers' => $this->_getSellers(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(array $data)
|
||||||
|
{
|
||||||
|
return Clients::create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update($id, array $data)
|
||||||
|
{
|
||||||
|
$client = $this->find($id);
|
||||||
|
$client->update($data);
|
||||||
|
return $client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($id): void
|
||||||
|
{
|
||||||
|
$client = $this->find($id);
|
||||||
|
$client->status = 'I';
|
||||||
|
$client->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchName(string $term)
|
||||||
|
{
|
||||||
|
return Clients::with('clientType')->where('type', 'client')->where('status', 'A')
|
||||||
|
->where('name', 'like', '%' . $term . '%')->orderBy('name')->limit(10)->get()
|
||||||
|
->map(fn ($client) => ['id' => $client->id, 'label' => $client->name . ' (' . optional($client->clientType)->description . ')']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchID($id)
|
||||||
|
{
|
||||||
|
abort_unless(ctype_digit((string) $id), 400, 'ID invalido');
|
||||||
|
return Clients::where('type', 'client')->where('status', 'A')->findOrFail((int) $id);
|
||||||
|
}
|
||||||
|
}
|
||||||
735
app/Services/ContractService.php
Normal file
735
app/Services/ContractService.php
Normal file
@@ -0,0 +1,735 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Exports\ContractsExport;
|
||||||
|
use App\Http\Requests\ContractRequest;
|
||||||
|
use App\Http\Requests\RenewContractRequest;
|
||||||
|
use App\Models\{
|
||||||
|
ClientType, Contract, ContractHistory, RenewalType
|
||||||
|
};
|
||||||
|
use App\Traits\ResourcesCache;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
|
||||||
|
class ContractService
|
||||||
|
{
|
||||||
|
|
||||||
|
use ResourcesCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a paginated list of contracts with applied filters.
|
||||||
|
*
|
||||||
|
* @param Request $request The HTTP request object containing filter and pagination data.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\View\View
|
||||||
|
*
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
* @version 2025/06
|
||||||
|
* @since 2025/06
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$perPage = max(1, min(100, (int) $request->get('per_page', 10)));
|
||||||
|
|
||||||
|
$query = Contract::query();
|
||||||
|
$this->_applyFilters($query, $request);
|
||||||
|
|
||||||
|
$filteredQuery = clone $query;
|
||||||
|
$contracts = $query->paginate($perPage)->appends($request->query());
|
||||||
|
|
||||||
|
# Values for the full filtered set, not only the current page
|
||||||
|
$totalPvp = (float) $filteredQuery->sum('contract_value');
|
||||||
|
$totalCommission = (float) $filteredQuery->sum('commission_value');
|
||||||
|
$totalChargeback = (float) $filteredQuery->sum('chargeback_value');
|
||||||
|
$totalMargin = $totalPvp - $totalCommission + $totalChargeback;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'contracts' => $contracts,
|
||||||
|
'statuses' => $this->_getContractStatuses(),
|
||||||
|
'contractTypes' => $this->_getContractTypes(),
|
||||||
|
'sellers' => $this->_getSellers(),
|
||||||
|
'totalPvp' => $totalPvp,
|
||||||
|
'totalCommission' => $totalCommission,
|
||||||
|
'totalChargeback' => $totalChargeback,
|
||||||
|
'totalMargin' => $totalMargin,
|
||||||
|
'suppliers' => $this->_getSuppliers(),
|
||||||
|
'clientTypes' => ClientType::orderBy('description')->get(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies filter parameters to the contract query.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @author Luis Medon
|
||||||
|
*/
|
||||||
|
private function _applyFilters($query, Request $request)
|
||||||
|
{
|
||||||
|
$filters = [
|
||||||
|
'contract_id' => fn($q, $v) => $q->where('id', $v),
|
||||||
|
'contract_code' => fn($q, $v) => $q->where('code', 'like', "%$v%"),
|
||||||
|
'status' => fn($q, $v) => $q->where('status_id', $v),
|
||||||
|
'contract_type' => fn($q, $v) => $q->where('contract_type_id', $v),
|
||||||
|
'client_type_id' => fn($q, $v) => $q->whereHas(
|
||||||
|
'client',
|
||||||
|
fn($clientQuery) => $clientQuery->where('client_type_id', $v)
|
||||||
|
),
|
||||||
|
'supply_type' => fn($q, $v) => $q->where('supply_type', $v),
|
||||||
|
'validity_from' => fn($q, $v) => $q->whereDate('contract_date', '>=', $this->normalizeDate($v)),
|
||||||
|
'validity_to' => fn($q, $v) => $q->whereDate('contract_date', '<=', $this->normalizeDate($v)),
|
||||||
|
'activation_from' => fn($q, $v) => $q->whereDate('activation_date', '>=', $this->normalizeDate($v)),
|
||||||
|
'activation_to' => fn($q, $v) => $q->whereDate('activation_date', '<=', $this->normalizeDate($v)),
|
||||||
|
'renewal_from' => fn($q, $v) => $q->whereDate('validity', '>=', $this->normalizeDate($v)),
|
||||||
|
'renewal_to' => fn($q, $v) => $q->whereDate('validity', '<=', $this->normalizeDate($v)),
|
||||||
|
'seller_id' => fn($q, $v) => $q->where('seller_id', $v),
|
||||||
|
'supplier_id' => fn($q, $v) => $q->where('new_supplier_id', $v),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($filters as $key => $callback) {
|
||||||
|
if ($request->filled($key)) {
|
||||||
|
$callback($query, $request->input($key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('client_name')) {
|
||||||
|
$query->whereHas('client', fn($q) => $q->where('name', 'like', '%' . $request->input('client_name') . '%'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('sort_by')) {
|
||||||
|
$allowedSortColumns = [
|
||||||
|
'id', 'code', 'status_id', 'contract_type_id', 'supply_type',
|
||||||
|
'validity', 'activation_date', 'renewal_date', 'seller_id'
|
||||||
|
];
|
||||||
|
$sortBy = $request->input('sort_by');
|
||||||
|
if (in_array($sortBy, $allowedSortColumns)) {
|
||||||
|
$order = strtolower($request->input('order', 'desc'));
|
||||||
|
$order = in_array($order, ['asc', 'desc']) ? $order : 'desc';
|
||||||
|
$query->orderBy($sortBy, $order);
|
||||||
|
} else {
|
||||||
|
// Fallback to default sorting if sort_by is invalid
|
||||||
|
$query->orderBy('activation_date', 'desc');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Always apply default sorting when sort_by is not provided
|
||||||
|
// Last added contracts first
|
||||||
|
$query->orderBy('id', 'desc');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports filtered contracts to Excel.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||||
|
*
|
||||||
|
* @author Luis Medon
|
||||||
|
*/
|
||||||
|
public function export(Request $request)
|
||||||
|
{
|
||||||
|
$filename = 'contratos_' . now()->format('Ymd_His') . '.xlsx';
|
||||||
|
return Excel::download(new ContractsExport($request), $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the contract creation form.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\View\View
|
||||||
|
*
|
||||||
|
* @author Luis Medon
|
||||||
|
*/
|
||||||
|
public function create(Request $request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'clientId' => $request->query('client_id'),
|
||||||
|
'contract' => null,
|
||||||
|
'paymentMethods' => $this->_getPaymentMethods(),
|
||||||
|
'contractTypes' => $this->_getContractTypes(),
|
||||||
|
'contractStatus' => $this->_getContractStatuses(),
|
||||||
|
'powerTiers' => $this->_getPowerTiers(),
|
||||||
|
'cui_tiers' => $this->_getCuiTiers(),
|
||||||
|
'suppliers' => $this->_getSuppliers(),
|
||||||
|
'typology' => $this->_getTypologies(),
|
||||||
|
'sellers' => $this->_getSellers(),
|
||||||
|
'installments_count' => 12,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a newly created contract.
|
||||||
|
*
|
||||||
|
* @param ContractRequest $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*
|
||||||
|
* @author Luis Medon
|
||||||
|
*/
|
||||||
|
public function store(ContractRequest $request)
|
||||||
|
{
|
||||||
|
$data = $request->validated();
|
||||||
|
|
||||||
|
if (! $request->user()?->can('view_contract_summary')) {
|
||||||
|
foreach (['contract_value', 'commission_value', 'profit_value', 'paid', 'chargeback', 'chargeback_value', 'chargeback_date'] as $field) {
|
||||||
|
unset($data[$field]);
|
||||||
|
}
|
||||||
|
$installmentsInput = [];
|
||||||
|
} else {
|
||||||
|
$installmentsInput = $request->input('installments', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (['contract_date', 'activation_date', 'validity', 'contract_end_date', 'renewal_date', 'payment_date', 'chargeback_date'] as $field) {
|
||||||
|
if (!empty($data[$field])) {
|
||||||
|
$data[$field] = $this->normalizeDate($data[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($data['delivery_method'] ?? null) !== 'email') {
|
||||||
|
$data['delivery_email'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($data['chargeback'])) {
|
||||||
|
$data['chargeback_value'] = null;
|
||||||
|
$data['chargeback_date'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->user()?->can('view_contract_summary')) {
|
||||||
|
$data['profit_value'] = (float) ($data['contract_value'] ?? 0)
|
||||||
|
- abs((float) ($data['commission_value'] ?? 0))
|
||||||
|
+ (float) ($data['chargeback_value'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$contract = Contract::create($data);
|
||||||
|
|
||||||
|
foreach ($installmentsInput as $i => $installment) {
|
||||||
|
if (!empty($installment['due_date']) && !empty($installment['amount'])) {
|
||||||
|
$dueDate = $this->normalizeDate($installment['due_date']);
|
||||||
|
if ($dueDate === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contract->installments()->create([
|
||||||
|
'installment_number' => $i,
|
||||||
|
'due_date' => $dueDate,
|
||||||
|
'amount' => $installment['amount'],
|
||||||
|
'paid' => isset($installment['paid']) && $installment['paid'] ? 1 : 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contract;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the contract editing form.
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
*
|
||||||
|
* @return \Illuminate\View\View
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||||
|
*
|
||||||
|
* @author Luis Medon
|
||||||
|
*/
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
|
||||||
|
$suppliers = $this->includeCurrentOption($this->_getSuppliers(), $contract->previousSupplier);
|
||||||
|
$suppliers = $this->includeCurrentOption($suppliers, $contract->newSupplier);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'contract' => $contract,
|
||||||
|
'clientId' => $contract->client_id,
|
||||||
|
'paymentMethods' => $this->includeCurrentOption($this->_getPaymentMethods(), $contract->paymentMethod),
|
||||||
|
'contractTypes' => $this->includeCurrentOption($this->_getContractTypes(), $contract->contractType),
|
||||||
|
'contractStatus' => $this->_getContractStatuses(),
|
||||||
|
'powerTiers' => $this->includeCurrentOption($this->_getPowerTiers(), $contract->powerTier),
|
||||||
|
'cui_tiers' => $this->includeCurrentOption($this->_getCuiTiers(), $contract->cuiTier),
|
||||||
|
'suppliers' => $suppliers,
|
||||||
|
'typology' => $this->includeCurrentOption($this->_getTypologies(), $contract->typology),
|
||||||
|
'sellers' => $this->includeCurrentOption($this->_getSellers(), $contract->seller),
|
||||||
|
'installments_count' => 12,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function includeCurrentOption(Collection $options, ?Model $current): Collection
|
||||||
|
{
|
||||||
|
$options = clone $options;
|
||||||
|
|
||||||
|
if ($current && ! $options->contains(fn (Model $option) => $option->is($current))) {
|
||||||
|
$options->prepend($current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing contract.
|
||||||
|
*
|
||||||
|
* @param ContractRequest $request
|
||||||
|
* @param string $id
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||||
|
*
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
*/
|
||||||
|
public function update(ContractRequest $request, string $id)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
$data = $request->validated();
|
||||||
|
|
||||||
|
if (! $request->user()?->can('view_contract_summary')) {
|
||||||
|
foreach (['contract_value', 'commission_value', 'profit_value', 'paid', 'chargeback', 'chargeback_value', 'chargeback_date'] as $field) {
|
||||||
|
unset($data[$field]);
|
||||||
|
}
|
||||||
|
$installmentsInput = [];
|
||||||
|
} else {
|
||||||
|
$installmentsInput = $request->input('installments', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Campos de data do contrato
|
||||||
|
foreach (['contract_date', 'activation_date', 'validity', 'contract_end_date', 'renewal_date', 'payment_date', 'chargeback_date'] as $field) {
|
||||||
|
if (!empty($data[$field])) {
|
||||||
|
$data[$field] = $this->normalizeDate($data[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($data['delivery_method'] ?? null) !== 'email') {
|
||||||
|
$data['delivery_email'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->user()?->can('view_contract_summary') && empty($data['chargeback'])) {
|
||||||
|
$data['chargeback_value'] = null;
|
||||||
|
$data['chargeback_date'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->user()?->can('view_contract_summary')) {
|
||||||
|
$data['profit_value'] = (float) ($data['contract_value'] ?? 0)
|
||||||
|
- abs((float) ($data['commission_value'] ?? 0))
|
||||||
|
+ (float) ($data['chargeback_value'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $contract->update($data)) {
|
||||||
|
throw \Illuminate\Validation\ValidationException::withMessages([
|
||||||
|
'contract' => 'O contrato não foi atualizado. Reveja os dados e tente novamente.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parcelas
|
||||||
|
$processedIds = [];
|
||||||
|
|
||||||
|
foreach ($installmentsInput as $i => $installment) {
|
||||||
|
$dueDateRaw = trim($installment['due_date'] ?? '');
|
||||||
|
$amount = $installment['amount'] ?? null;
|
||||||
|
|
||||||
|
if ($dueDateRaw !== '' && $amount !== null) {
|
||||||
|
$dueDate = $this->normalizeDate($dueDateRaw);
|
||||||
|
if ($dueDate === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'installment_number' => $i,
|
||||||
|
'due_date' => $dueDate,
|
||||||
|
'amount' => $amount,
|
||||||
|
'paid' => isset($installment['paid']) && $installment['paid'] ? 1 : 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
$existing = $contract->installments()->where('installment_number', $i)->first();
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
$existing->update($payload);
|
||||||
|
$processedIds[] = $existing->id;
|
||||||
|
} else {
|
||||||
|
$new = $contract->installments()->create($payload);
|
||||||
|
$processedIds[] = $new->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleta parcelas não enviadas
|
||||||
|
if ($request->user()?->can('view_contract_summary')) {
|
||||||
|
$contract->installments()->whereNotIn('id', $processedIds)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contract;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows contract details including financial summary.
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
*
|
||||||
|
* @return \Illuminate\View\View
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||||
|
*
|
||||||
|
* @author Luis Medon
|
||||||
|
*/
|
||||||
|
public function show(string $id)
|
||||||
|
{
|
||||||
|
$contract = Contract::with('payments')->findOrFail($id);
|
||||||
|
$lastRef = $contract->payments()->orderByDesc('reference_period')->value('reference_period');
|
||||||
|
|
||||||
|
$totalReceived = $totalPending = $commission = $profit = 0;
|
||||||
|
|
||||||
|
if ($lastRef) {
|
||||||
|
$payments = $contract->payments()->whereDate('reference_period', $lastRef)->get();
|
||||||
|
$totalReceived = $payments->where('status', 'paid')->sum('amount');
|
||||||
|
$totalPending = $payments->where('status', 'pending')->sum('amount');
|
||||||
|
$commission = $contract->pvp * ($contract->commission_rate / 100);
|
||||||
|
$profit = ($contract->pvp - $commission) - $contract->net
|
||||||
|
+ (float) ($contract->chargeback_value ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'contract' => $contract,
|
||||||
|
'contractTypes' => $this->_getContractTypes(),
|
||||||
|
'statuses' => $this->_getContractStatuses(),
|
||||||
|
'totalReceived' => $totalReceived,
|
||||||
|
'totalPending' => $totalPending,
|
||||||
|
'commission' => $commission,
|
||||||
|
'profit' => $profit,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the contract renewal form.
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
*
|
||||||
|
* @return \Illuminate\View\View
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||||
|
*
|
||||||
|
* @author Luis Medon
|
||||||
|
*/
|
||||||
|
public function renewForm(string $id)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
return [
|
||||||
|
'contract' => $contract,
|
||||||
|
'renewalTypes' => RenewalType::orderBy('id')->get(),
|
||||||
|
'suppliers' => $this->_getSuppliers(),
|
||||||
|
'contractTypes' => $this->_getContractTypes(),
|
||||||
|
'typology' => $this->_getTypologies(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs contract renewal and creates payment.
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||||
|
*
|
||||||
|
* @author Luis Medon <luismedon@gmail.com>
|
||||||
|
*/
|
||||||
|
public function renew(RenewContractRequest $request, $id)
|
||||||
|
{
|
||||||
|
# Obtain contract information
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
|
||||||
|
# Obtain contract installemnts
|
||||||
|
$installments = $contract->installments()->get();
|
||||||
|
$totalInstallments = $installments->count();
|
||||||
|
$totalPaidInstallments = $installments->where('paid', true)->count();
|
||||||
|
$canViewFinancials = $request->user()?->can('view_contract_summary') ?? false;
|
||||||
|
$currentSupplierId = $request->boolean('change_supplier')
|
||||||
|
? $request->new_supplier_id
|
||||||
|
: $contract->new_supplier_id;
|
||||||
|
|
||||||
|
# Create Contract History Record
|
||||||
|
$historyData = [
|
||||||
|
'contract_id' => $contract->id,
|
||||||
|
'previous_supplier_id' => $contract->new_supplier_id,
|
||||||
|
'current_supplier_id' => $currentSupplierId,
|
||||||
|
'seller_id' => $contract->seller_id,
|
||||||
|
'contract_type_id' => $request->contract_type_id,
|
||||||
|
'typology_id' => $request->typology_id,
|
||||||
|
'contract_date' => $contract->contract_date,
|
||||||
|
'activation_date' => $contract->activation_date,
|
||||||
|
'renewal_date' => $contract->validity,
|
||||||
|
'renewal_type' => $request->renewal_type,
|
||||||
|
'go_ki' => $contract->go_ki,
|
||||||
|
'validity' => $contract->validity,
|
||||||
|
'contract_value' => $contract->contract_value,
|
||||||
|
'commission_value' => $contract->commission_value,
|
||||||
|
'profit_value' => $contract->profit_value,
|
||||||
|
'annual_consumption' => $contract->annual_consumption,
|
||||||
|
'total_installments' => $totalInstallments,
|
||||||
|
'paid_installments' => $totalPaidInstallments,
|
||||||
|
'notes' => 'Renovação automática',
|
||||||
|
'contract_snapshot' => $contract->toJson(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($canViewFinancials) {
|
||||||
|
$historyData['chargeback'] = (bool) $contract->chargeback;
|
||||||
|
$historyData['chargeback_value'] = $contract->chargeback_value;
|
||||||
|
$historyData['chargeback_date'] = $contract->chargeback_date;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContractHistory::create($historyData);
|
||||||
|
|
||||||
|
# Update current contract data
|
||||||
|
$contractData = [
|
||||||
|
'annual_consumption' => $request->annual_consumption,
|
||||||
|
'contract_date' => $request->contract_date,
|
||||||
|
'activation_date' => $request->activation_date,
|
||||||
|
'contract_end_date' => $request->contract_end_date,
|
||||||
|
'validity' => $request->next_due_date,
|
||||||
|
'contract_type_id' => $request->contract_type_id,
|
||||||
|
'typology_id' => $request->typology_id,
|
||||||
|
'go_ki' => $request->go_ki,
|
||||||
|
'paid' => true,
|
||||||
|
'previous_supplier_id' => $contract->new_supplier_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($canViewFinancials) {
|
||||||
|
$contractData['contract_value'] = $request->amount_paid;
|
||||||
|
$contractData['commission_value'] = $request->commission_value;
|
||||||
|
$contractData['profit_value'] = (float) $request->amount_paid - abs((float) $request->commission_value) + (float) ($contract->chargeback_value ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->boolean('change_supplier')) {
|
||||||
|
$contractData['new_supplier_id'] = $request->new_supplier_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contract->update($contractData);
|
||||||
|
|
||||||
|
return $contract;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function history($id)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
|
||||||
|
$histories = ContractHistory::with(['previousSupplier', 'currentSupplier', 'seller', 'contractType'])
|
||||||
|
->where('contract_id', $contract->id)
|
||||||
|
->orderByRaw('COALESCE(validity, contract_date, created_at) ASC')
|
||||||
|
->paginate(10);
|
||||||
|
|
||||||
|
$totals = ContractHistory::where('contract_id', $contract->id)
|
||||||
|
->selectRaw('COUNT(*) as renewals_count')
|
||||||
|
->selectRaw('COALESCE(SUM(total_installments), 0) as total_installments')
|
||||||
|
->selectRaw('COALESCE(SUM(paid_installments), 0) as paid_installments')
|
||||||
|
->selectRaw('COALESCE(SUM(contract_value), 0) as contract_value')
|
||||||
|
->selectRaw('COALESCE(SUM(commission_value), 0) as commission_value')
|
||||||
|
->selectRaw('COALESCE(SUM(chargeback_value), 0) as chargeback_value')
|
||||||
|
->selectRaw('COALESCE(SUM(profit_value), 0) as profit_value')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return compact('histories', 'totals', 'contract');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createHistory($id)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'contract' => $contract,
|
||||||
|
'history' => null,
|
||||||
|
'suppliers' => $this->_getSuppliers(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeHistory(Request $request, $id)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'previous_supplier_id' => ['nullable', 'exists:suppliers,id'],
|
||||||
|
'current_supplier_id' => ['nullable', 'exists:suppliers,id'],
|
||||||
|
'validity' => ['nullable', 'date'],
|
||||||
|
'annual_consumption' => ['nullable', 'numeric'],
|
||||||
|
'seller_id' => ['nullable', 'exists:users,id'],
|
||||||
|
'contract_type_id' => ['nullable', 'exists:contract_types,id'],
|
||||||
|
'typology_id' => ['nullable', 'exists:typologies,id'],
|
||||||
|
'contract_date' => ['nullable', 'date'],
|
||||||
|
'activation_date' => ['nullable', 'date'],
|
||||||
|
'renewal_date' => ['nullable', 'date'],
|
||||||
|
'renewal_type' => ['nullable', 'string'],
|
||||||
|
'go_ki' => ['nullable', 'numeric'],
|
||||||
|
'contract_value' => ['nullable', 'numeric'],
|
||||||
|
'commission_value' => ['nullable', 'numeric'],
|
||||||
|
'profit_value' => ['nullable', 'numeric'],
|
||||||
|
'chargeback' => ['nullable', 'boolean'],
|
||||||
|
'chargeback_value' => ['nullable', 'numeric', 'max:-0.01'],
|
||||||
|
'chargeback_date' => ['nullable', 'date'],
|
||||||
|
'notes' => ['nullable', 'string'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $request->user()?->can('view_contract_summary')) {
|
||||||
|
foreach (['contract_value', 'commission_value', 'profit_value', 'chargeback', 'chargeback_value', 'chargeback_date'] as $field) {
|
||||||
|
unset($rules[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate($rules);
|
||||||
|
|
||||||
|
foreach (['validity', 'contract_date', 'activation_date', 'renewal_date', 'chargeback_date'] as $field) {
|
||||||
|
if (! empty($data[$field])) {
|
||||||
|
$data[$field] = $this->normalizeDate($data[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($data['chargeback'] ?? null)) {
|
||||||
|
$data['chargeback'] = false;
|
||||||
|
$data['chargeback_value'] = null;
|
||||||
|
$data['chargeback_date'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['contract_id'] = $contract->id;
|
||||||
|
|
||||||
|
if ($request->user()?->can('view_contract_summary') && ! empty($data['contract_value']) && ! empty($data['commission_value'])) {
|
||||||
|
$data['profit_value'] = (float) ($data['contract_value'] ?? 0)
|
||||||
|
- abs((float) ($data['commission_value'] ?? 0))
|
||||||
|
+ (float) ($data['chargeback_value'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContractHistory::create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function editHistory($id, $historyId)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
$history = ContractHistory::where('contract_id', $contract->id)->findOrFail($historyId);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'contract' => $contract,
|
||||||
|
'history' => $history,
|
||||||
|
'suppliers' => $this->_getSuppliers(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeDate($value): ?string
|
||||||
|
{
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = trim((string) $value);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
|
||||||
|
return Carbon::createFromFormat('Y-m-d', $value)->format('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $value)) {
|
||||||
|
return Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Carbon::parse($value)->format('Y-m-d');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateHistory(Request $request, $id, $historyId)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
$history = ContractHistory::where('contract_id', $contract->id)->findOrFail($historyId);
|
||||||
|
|
||||||
|
$data = $request->validate($this->getHistoryUpdateRules($request));
|
||||||
|
|
||||||
|
if (! $request->user()?->can('view_contract_summary')) {
|
||||||
|
foreach (['contract_value', 'commission_value', 'profit_value', 'chargeback', 'chargeback_value', 'chargeback_date'] as $field) {
|
||||||
|
unset($data[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$history->update($data);
|
||||||
|
|
||||||
|
return $history;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHistoryUpdateRules(Request $request): array
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'previous_supplier_id' => ['nullable', 'exists:suppliers,id'],
|
||||||
|
'validity' => ['nullable', 'date'],
|
||||||
|
'annual_consumption' => ['nullable', 'numeric'],
|
||||||
|
'notes' => ['nullable', 'string'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->user()?->can('view_contract_summary')) {
|
||||||
|
$rules['contract_value'] = ['required', 'numeric'];
|
||||||
|
$rules['commission_value'] = ['nullable', 'numeric'];
|
||||||
|
$rules['profit_value'] = ['nullable', 'numeric'];
|
||||||
|
$rules['chargeback'] = ['nullable', 'boolean'];
|
||||||
|
$rules['chargeback_value'] = ['nullable', 'numeric', 'max:-0.01'];
|
||||||
|
$rules['chargeback_date'] = ['nullable', 'date'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyHistory($id, $historyId)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
$history = ContractHistory::where('contract_id', $contract->id)->findOrFail($historyId);
|
||||||
|
$history->delete();
|
||||||
|
|
||||||
|
return $history;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a contract from the system.
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||||
|
*
|
||||||
|
* @author Luis Medon
|
||||||
|
*/
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
$contract = Contract::findOrFail($id);
|
||||||
|
$contract->delete();
|
||||||
|
|
||||||
|
return ['success' => true, 'message' => 'Contrato removido com sucesso.', 'icon' => 'success'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bulkDestroy(Request $request)
|
||||||
|
{
|
||||||
|
$data = $request->validate([
|
||||||
|
'ids' => ['required', 'array', 'min:1'],
|
||||||
|
'ids.*' => ['integer', 'distinct', 'exists:contracts,id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::transaction(function () use ($data) {
|
||||||
|
Contract::whereIn('id', $data['ids'])->get()->each->delete();
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'message' => count($data['ids']) === 1
|
||||||
|
? 'Contrato removido com sucesso.'
|
||||||
|
: 'Contratos removidos com sucesso.',
|
||||||
|
'icon' => 'success',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
79
app/Services/HomeService.php
Normal file
79
app/Services/HomeService.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Contract;
|
||||||
|
|
||||||
|
class HomeService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the application dashboard.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\Support\Renderable
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$today = now();
|
||||||
|
$next60Days = now()->addDays(60);
|
||||||
|
|
||||||
|
$activeStatus = 1;
|
||||||
|
$pendingStatus = 3;
|
||||||
|
|
||||||
|
// === Contracts grouped by client type (for the chart) ===
|
||||||
|
$contractsByClientType = Contract::with('client.clientType')
|
||||||
|
->where('status_id', $activeStatus)
|
||||||
|
->get()
|
||||||
|
->groupBy(function ($contract) {
|
||||||
|
return $contract->client->clientType->description ?? 'Undefined';
|
||||||
|
})
|
||||||
|
->map(function ($group) {
|
||||||
|
return $group->count();
|
||||||
|
});
|
||||||
|
|
||||||
|
$clientLabels = $contractsByClientType->keys();
|
||||||
|
$clientData = $contractsByClientType->values();
|
||||||
|
|
||||||
|
// === Pending Contracts ===
|
||||||
|
$pendingContracts = Contract::with(['client', 'contractType'])
|
||||||
|
->where('status_id', $pendingStatus)
|
||||||
|
->orderBy('id')
|
||||||
|
->take(5)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// === Active Contracts ===
|
||||||
|
$activeContracts = Contract::with(['client', 'contractType'])
|
||||||
|
->where('status_id', $activeStatus)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->take(5)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// === Upcoming Renewals (next 2 month) ===
|
||||||
|
$upcomingRenewals = Contract::with(['client', 'contractType', 'seller', 'installments' => function ($query) use ($today, $next60Days) {
|
||||||
|
$query->whereBetween('due_date', [$today, $next60Days])
|
||||||
|
->orderBy('due_date');
|
||||||
|
}])
|
||||||
|
->where('status_id', $activeStatus)
|
||||||
|
->whereBetween('validity', [$today, $next60Days])
|
||||||
|
->orderByDesc('contracts.id')
|
||||||
|
->take(5)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'clientLabels' => $clientLabels,
|
||||||
|
'clientData' => $clientData,
|
||||||
|
'activeContracts' => $activeContracts,
|
||||||
|
'pendingContracts' => $pendingContracts,
|
||||||
|
'upcomingRenewals' => $upcomingRenewals
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
133
app/Services/LeadService.php
Normal file
133
app/Services/LeadService.php
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Http\Requests\LeadRequest;
|
||||||
|
use App\Models\Clients as Client;
|
||||||
|
use App\Models\LeadDetail;
|
||||||
|
use App\Models\LeadStatus;
|
||||||
|
|
||||||
|
class LeadService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of leads.
|
||||||
|
*/
|
||||||
|
public function list()
|
||||||
|
{
|
||||||
|
// Fetch only records with type = 'lead' and eager load leadStatus relation
|
||||||
|
$leads = Client::with('leadStatus')
|
||||||
|
->where('type', 'lead')
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->paginate(10);
|
||||||
|
|
||||||
|
return compact('leads');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a specific lead.
|
||||||
|
*/
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
// Fetch a single lead with its leadStatus
|
||||||
|
$lead = Client::with(['leadStatus', 'leadDetail'])->where('type', 'lead')->findOrFail($id);
|
||||||
|
|
||||||
|
return compact('lead');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new lead.
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
// Use unified add_edit view
|
||||||
|
return ['leadStatuses' => LeadStatus::all()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created lead in storage.
|
||||||
|
*/
|
||||||
|
public function store(LeadRequest $request)
|
||||||
|
{
|
||||||
|
$validated = $request->validated();
|
||||||
|
$validated['type'] = 'lead';
|
||||||
|
$validated['seller_id'] = $request->user()->id;
|
||||||
|
|
||||||
|
$client = Client::create($validated);
|
||||||
|
|
||||||
|
// Save lead details
|
||||||
|
LeadDetail::create([
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'contact_date' => $request->contact_date,
|
||||||
|
'contract_end_date' => $request->contract_end_date,
|
||||||
|
'contract_type' => $request->contract_type,
|
||||||
|
'delivery_point_code' => $request->delivery_point_code,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $client->load('leadDetail');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified lead.
|
||||||
|
*/
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
// Find lead by id
|
||||||
|
$lead = Client::with('leadDetail')->where('type', 'lead')->findOrFail($id);
|
||||||
|
|
||||||
|
// Use unified add_edit view and pass the lead
|
||||||
|
return ['lead' => $lead, 'leadStatuses' => LeadStatus::all()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified lead in storage.
|
||||||
|
*/
|
||||||
|
public function update(LeadRequest $request, $id)
|
||||||
|
{
|
||||||
|
$lead = Client::where('type', 'lead')->findOrFail($id);
|
||||||
|
|
||||||
|
$lead->update($request->validated());
|
||||||
|
|
||||||
|
// Update or create lead details
|
||||||
|
$lead->leadDetail()->updateOrCreate(
|
||||||
|
['client_id' => $lead->id],
|
||||||
|
[
|
||||||
|
'contact_date' => $request->contact_date,
|
||||||
|
'contract_end_date' => $request->contract_end_date,
|
||||||
|
'contract_type' => $request->contract_type,
|
||||||
|
'delivery_point_code' => $request->delivery_point_code,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $lead->load('leadDetail');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a lead into a client.
|
||||||
|
*/
|
||||||
|
public function convert($id)
|
||||||
|
{
|
||||||
|
$lead = Client::where('type', 'lead')->findOrFail($id);
|
||||||
|
|
||||||
|
$lead->update([
|
||||||
|
'type' => 'client',
|
||||||
|
'lead_status_id' => null,
|
||||||
|
'status' => 'A',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $lead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified lead from storage.
|
||||||
|
*/
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$lead = Client::where('type', 'lead')->findOrFail($id);
|
||||||
|
$lead->delete();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Lead removed successfully!'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
78
app/Services/ResourceService.php
Normal file
78
app/Services/ResourceService.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\{PowerTier, ContractType, PaymentMethod, Typology, CuiTier, Supplier, ContractStatus};
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ResourceService
|
||||||
|
{
|
||||||
|
private const MODELS = [
|
||||||
|
'power_tiers' => PowerTier::class,
|
||||||
|
'contract_types' => ContractType::class,
|
||||||
|
'payment_methods' => PaymentMethod::class,
|
||||||
|
'typologies' => Typology::class,
|
||||||
|
'cui_tiers' => CuiTier::class,
|
||||||
|
'suppliers' => Supplier::class,
|
||||||
|
'contract-statuses' => ContractStatus::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
private function model(string $resource): string
|
||||||
|
{
|
||||||
|
abort_unless(isset(self::MODELS[$resource]), 404);
|
||||||
|
return self::MODELS[$resource];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function all(string $resource)
|
||||||
|
{
|
||||||
|
return $this->model($resource)::orderBy('id')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find(string $resource, $id): Model
|
||||||
|
{
|
||||||
|
$model = $this->model($resource)::findOrFail($id);
|
||||||
|
if ($resource === 'suppliers') {
|
||||||
|
abort_unless($model->status === 'A', 403, 'Este fornecedor esta inativo.');
|
||||||
|
}
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(string $resource, Request $request, $id = null): Model
|
||||||
|
{
|
||||||
|
$modelClass = $this->model($resource);
|
||||||
|
$model = $id === null ? new $modelClass() : $this->find($resource, $id);
|
||||||
|
$rules = match ($resource) {
|
||||||
|
'contract_types', 'typologies' => ['description' => 'required|string|max:255'],
|
||||||
|
'payment_methods' => ['name' => 'required|string|max:255', 'icon' => 'required|string|max:255'],
|
||||||
|
'suppliers' => ['name' => 'required|string|max:255', 'color' => 'nullable|string|max:20'],
|
||||||
|
'contract-statuses' => [
|
||||||
|
'code' => ['required', 'string', 'max:10', Rule::unique($model->getTable(), 'code')->ignore($model->getKey())],
|
||||||
|
'description' => 'required|string|max:255',
|
||||||
|
'color' => ['nullable', 'string', 'regex:/^#([A-Fa-f0-9]{6})$/'],
|
||||||
|
],
|
||||||
|
default => ['name' => 'required|string|max:255'],
|
||||||
|
};
|
||||||
|
$model->fill($request->validate($rules));
|
||||||
|
if ($resource === 'suppliers' && $id === null) {
|
||||||
|
$model->status = 'A';
|
||||||
|
}
|
||||||
|
$model->save();
|
||||||
|
$this->invalidate($resource);
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(string $resource, $id): void
|
||||||
|
{
|
||||||
|
$model = $this->model($resource)::findOrFail($id);
|
||||||
|
$resource === 'contract-statuses' ? $model->delete() : $model->update(['status' => 'I']);
|
||||||
|
$this->invalidate($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function invalidate(string $resource): void
|
||||||
|
{
|
||||||
|
Cache::forget($resource === 'contract-statuses' ? 'contract_statuses' : $resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Services/RolePermissionService.php
Normal file
25
app/Services/RolePermissionService.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
|
|
||||||
|
class RolePermissionService
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'roles' => Role::with('permissions')->get(),
|
||||||
|
'permissions' => Permission::all(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePermissions(Request $request, Role $role)
|
||||||
|
{
|
||||||
|
$request->validate(['permissions' => 'sometimes|array', 'permissions.*' => 'exists:permissions,name']);
|
||||||
|
$role->syncPermissions($request->input('permissions', []));
|
||||||
|
return $role->load('permissions');
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Services/UserAccessService.php
Normal file
25
app/Services/UserAccessService.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\User;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
class UserAccessService
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'users' => User::with('roles')->get(),
|
||||||
|
'roles' => Role::all(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateRoles(Request $request, User $user)
|
||||||
|
{
|
||||||
|
$request->validate(['roles' => 'sometimes|array', 'roles.*' => 'exists:roles,name']);
|
||||||
|
$user->syncRoles($request->input('roles', []));
|
||||||
|
return $user->load('roles');
|
||||||
|
}
|
||||||
|
}
|
||||||
139
app/Services/UserService.php
Normal file
139
app/Services/UserService.php
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use App\Mail\NewUserWelcome;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
class UserService
|
||||||
|
{
|
||||||
|
public function __construct(private AuthService $authentication)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$users = User::with('roles')->paginate(20);
|
||||||
|
return compact('users');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$roles = Role::where('id', '!=', 1)->get();
|
||||||
|
return compact('roles');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|unique:users,email',
|
||||||
|
'roles' => 'array|nullable',
|
||||||
|
'roles.*' => 'exists:roles,name',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$validator->validate();
|
||||||
|
|
||||||
|
$canLogin = $request->boolean('can_login');
|
||||||
|
$password = Str::random(12);
|
||||||
|
|
||||||
|
$user = User::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'password' => Hash::make($password),
|
||||||
|
'can_login' => $canLogin,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->filled('roles')) {
|
||||||
|
$user->syncRoles($request->roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($canLogin) {
|
||||||
|
Mail::to($user->email)->send(new NewUserWelcome($user, $password));
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache::forget('sellers');
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(User $user)
|
||||||
|
{
|
||||||
|
$roles = Role::where('id', '!=', 1)->get();
|
||||||
|
return compact('user', 'roles');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, User $user)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|unique:users,email,' . $user->id,
|
||||||
|
'roles' => 'array|nullable',
|
||||||
|
'roles.*' => 'exists:roles,name',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$validator->validate();
|
||||||
|
|
||||||
|
$user->update([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'can_login' => $request->boolean('can_login'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->has('roles')) {
|
||||||
|
$user->syncRoles($request->input('roles', []));
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache::forget('sellers');
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendPasswordResetLink(User $user)
|
||||||
|
{
|
||||||
|
$status = $this->authentication->sendResetLink(['email' => $user->email]);
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function savePin(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'pin' => ['required', 'digits:4'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user->pin = Hash::make($request->pin);
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
return ['message' => 'PIN saved successfully.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkPin(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'pin' => ['required', 'digits:4']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
if ($user->pin && Hash::check($request->pin, $user->pin)) {
|
||||||
|
return ['message' => 'PIN valid'];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw \Illuminate\Validation\ValidationException::withMessages(['pin' => 'Invalid PIN']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(User $user): void
|
||||||
|
{
|
||||||
|
$user->tokens()->delete();
|
||||||
|
$user->delete();
|
||||||
|
Cache::forget('sellers');
|
||||||
|
}
|
||||||
|
}
|
||||||
130
app/Traits/ResourcesCache.php
Normal file
130
app/Traits/ResourcesCache.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use App\Models\{
|
||||||
|
CuiTier, Clients, ContractStatus,
|
||||||
|
ContractType, PaymentMethod,
|
||||||
|
PowerTier, Supplier, Typology, User
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait ResourcesCache
|
||||||
|
*
|
||||||
|
* This trait provides cached access to frequently used resources
|
||||||
|
* such as contract statuses, contract types, payment methods,
|
||||||
|
* suppliers, and others. It helps to avoid hitting the database
|
||||||
|
* repeatedly by caching results for a configurable period of time.
|
||||||
|
*/
|
||||||
|
trait ResourcesCache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Defines the cache lifetime in seconds.
|
||||||
|
*
|
||||||
|
* It retrieves the session lifetime from the configuration
|
||||||
|
* and converts it into seconds (minutes * 60).
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function cacheTime(): int
|
||||||
|
{
|
||||||
|
return config('session.lifetime') * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all contract statuses, cached.
|
||||||
|
*/
|
||||||
|
public function _getContractStatuses()
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
'contract_statuses',
|
||||||
|
$this->cacheTime(),
|
||||||
|
fn() => ContractStatus::all()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active contract types, cached.
|
||||||
|
*/
|
||||||
|
public function _getContractTypes()
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
'contract_types',
|
||||||
|
$this->cacheTime(),
|
||||||
|
fn() => ContractType::where('status', 'A')->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active payment methods, cached.
|
||||||
|
*/
|
||||||
|
public function _getPaymentMethods()
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
'payment_methods',
|
||||||
|
$this->cacheTime(),
|
||||||
|
fn() => PaymentMethod::where('status', 'A')->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active power tiers, cached.
|
||||||
|
*/
|
||||||
|
public function _getPowerTiers()
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
'power_tiers',
|
||||||
|
$this->cacheTime(),
|
||||||
|
fn() => PowerTier::where('status', 'A')->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active CUI tiers, cached.
|
||||||
|
*/
|
||||||
|
public function _getCuiTiers()
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
'cui_tiers',
|
||||||
|
$this->cacheTime(),
|
||||||
|
fn() => CuiTier::where('status', 'A')->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active suppliers, cached.
|
||||||
|
*/
|
||||||
|
public function _getSuppliers()
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
'suppliers',
|
||||||
|
$this->cacheTime(),
|
||||||
|
fn() => Supplier::where('status', 'A')->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active typologies, cached.
|
||||||
|
*/
|
||||||
|
public function _getTypologies()
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
'typologies',
|
||||||
|
$this->cacheTime(),
|
||||||
|
fn() => Typology::where('status', 'A')->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all sellers except the one with ID = 1, cached.
|
||||||
|
*/
|
||||||
|
public function _getSellers()
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
'sellers',
|
||||||
|
$this->cacheTime(),
|
||||||
|
fn() => User::where('id', '!=', 1)->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
artisan
Normal file
18
artisan
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
|
||||||
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
|
// Register the Composer autoloader...
|
||||||
|
require __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
|
// Bootstrap Laravel and handle the command...
|
||||||
|
/** @var Application $app */
|
||||||
|
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||||
|
|
||||||
|
$status = $app->handleCommand(new ArgvInput);
|
||||||
|
|
||||||
|
exit($status);
|
||||||
79
bootstrap/app.php
Normal file
79
bootstrap/app.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
|
||||||
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
|
->withRouting(
|
||||||
|
web: __DIR__.'/../routes/web.php',
|
||||||
|
api: __DIR__.'/../routes/api.php',
|
||||||
|
commands: __DIR__.'/../routes/console.php',
|
||||||
|
health: '/up',
|
||||||
|
)
|
||||||
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|
|
||||||
|
$middleware->api(prepend: [\App\Http\Middleware\ApiResponse::class]);
|
||||||
|
|
||||||
|
# Register Middleware
|
||||||
|
$middleware->alias([
|
||||||
|
'auth.session' => \App\Http\Middleware\AuthSession::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
})
|
||||||
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
|
$exceptions->reportable(function (Throwable $e) {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$exceptions->render(function (Throwable $e, $request) {
|
||||||
|
if ($request->is('api/*') || $request->expectsJson()) {
|
||||||
|
$status = 500;
|
||||||
|
$message = $e->getMessage() ?: 'Erro interno do servidor';
|
||||||
|
|
||||||
|
if ($e instanceof \Illuminate\Validation\ValidationException) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Erro de validação.',
|
||||||
|
'errors' => $e->errors(),
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($e instanceof \Illuminate\Auth\AuthenticationException) {
|
||||||
|
return response()->json(['message' => 'Nao autenticado.'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($e instanceof \Illuminate\Auth\Access\AuthorizationException) {
|
||||||
|
return response()->json(['message' => 'Acesso negado.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
|
||||||
|
return response()->json(['message' => 'Registo nao encontrado.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($e instanceof \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface) {
|
||||||
|
$status = $e->getStatusCode();
|
||||||
|
$message = $e->getMessage() ?: 'Erro HTTP';
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => $status >= 500 && app()->environment('production') ? 'Erro interno do servidor.' : $message,
|
||||||
|
'exception' => app()->environment('production') ? null : get_class($e),
|
||||||
|
], $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->withSchedule(function (Schedule $schedule) {
|
||||||
|
// $schedule->command('backup:run')->dailyAt('13:00');
|
||||||
|
// $schedule->command('backup:clean-minio')->dailyAt('13:30');
|
||||||
|
$schedule->command('sanctum:prune-expired --hours=24')->dailyAt('05:00');
|
||||||
|
})
|
||||||
|
->booted(function () {
|
||||||
|
if (config('app.force_ssl')) {
|
||||||
|
URL::forceScheme('https');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->create();
|
||||||
2
bootstrap/cache/.gitignore
vendored
Normal file
2
bootstrap/cache/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
6
bootstrap/providers.php
Normal file
6
bootstrap/providers.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
App\Providers\AppServiceProvider::class,
|
||||||
|
App\Providers\TelescopeServiceProvider::class,
|
||||||
|
];
|
||||||
82
composer.json
Normal file
82
composer.json
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://getcomposer.org/schema.json",
|
||||||
|
"name": "laravel/laravel",
|
||||||
|
"type": "project",
|
||||||
|
"description": "The skeleton application for the Laravel framework.",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"framework"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"require": {
|
||||||
|
"php": "^8.2",
|
||||||
|
"doctrine/dbal": "^4.3",
|
||||||
|
"laravel/framework": "^12.0",
|
||||||
|
"laravel/sanctum": "^4.2",
|
||||||
|
"laravel/tinker": "^2.10.1",
|
||||||
|
"laravel/ui": "^4.6",
|
||||||
|
"league/flysystem-aws-s3-v3": "^3.0",
|
||||||
|
"maatwebsite/excel": "^3.1",
|
||||||
|
"nesbot/carbon": "^3.9",
|
||||||
|
"spatie/laravel-backup": "^9.3",
|
||||||
|
"spatie/laravel-permission": "^6.17"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"fakerphp/faker": "^1.24",
|
||||||
|
"laravel/pail": "^1.2.2",
|
||||||
|
"laravel/pint": "^1.13",
|
||||||
|
"laravel/sail": "^1.41",
|
||||||
|
"mockery/mockery": "^1.6",
|
||||||
|
"nunomaduro/collision": "^8.6",
|
||||||
|
"phpunit/phpunit": "^11.5.3"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "app/",
|
||||||
|
"Database\\Factories\\": "database/factories/",
|
||||||
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"post-autoload-dump": [
|
||||||
|
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||||
|
"@php artisan package:discover --ansi"
|
||||||
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||||
|
],
|
||||||
|
"post-root-package-install": [
|
||||||
|
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||||
|
],
|
||||||
|
"post-create-project-cmd": [
|
||||||
|
"@php artisan key:generate --ansi",
|
||||||
|
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
|
||||||
|
"@php artisan migrate --graceful --ansi"
|
||||||
|
],
|
||||||
|
"dev": [
|
||||||
|
"Composer\\Config::disableProcessTimeout",
|
||||||
|
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"dont-discover": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"preferred-install": "dist",
|
||||||
|
"sort-packages": true,
|
||||||
|
"allow-plugins": {
|
||||||
|
"pestphp/pest-plugin": true,
|
||||||
|
"php-http/discovery": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
||||||
10102
composer.lock
generated
Normal file
10102
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
234
config/app.php
Normal file
234
config/app.php
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
|
||||||
|
'force_ssl' => env('FORCE_SSL', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value is the name of your application, which will be used when the
|
||||||
|
| framework needs to place the application's name in a notification or
|
||||||
|
| other UI elements where an application name needs to be displayed.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'name' => env('APP_NAME', 'Laravel'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Environment
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value determines the "environment" your application is currently
|
||||||
|
| running in. This may determine how you prefer to configure various
|
||||||
|
| services the application utilizes. Set this in your ".env" file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'env' => env('APP_ENV', 'production'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Debug Mode
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When your application is in debug mode, detailed error messages with
|
||||||
|
| stack traces will be shown on every error that occurs within your
|
||||||
|
| application. If disabled, a simple generic error page is shown.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'debug' => (bool) env('APP_DEBUG', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application URL
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This URL is used by the console to properly generate URLs when using
|
||||||
|
| the Artisan command line tool. You should set this to the root of
|
||||||
|
| the application so that it's available within Artisan commands.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'url' => env('APP_URL', 'http://localhost'),
|
||||||
|
|
||||||
|
'api_url' => env('API_URL', 'http://localhost/api'),
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Timezone
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify the default timezone for your application, which
|
||||||
|
| will be used by the PHP date and date-time functions. The timezone
|
||||||
|
| is set to "UTC" by default as it is suitable for most use cases.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Locale Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The application locale determines the default locale that will be used
|
||||||
|
| by Laravel's translation / localization methods. This option can be
|
||||||
|
| set to any locale for which you plan to have translation strings.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'locale' => env('APP_LOCALE', 'pt'),
|
||||||
|
|
||||||
|
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'pt'),
|
||||||
|
|
||||||
|
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Encryption Key
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This key is utilized by Laravel's encryption services and should be set
|
||||||
|
| to a random, 32 character string to ensure that all encrypted values
|
||||||
|
| are secure. You should do this prior to deploying the application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'cipher' => 'AES-256-CBC',
|
||||||
|
|
||||||
|
'key' => env('APP_KEY'),
|
||||||
|
|
||||||
|
'previous_keys' => [
|
||||||
|
...array_filter(
|
||||||
|
explode(',', env('APP_PREVIOUS_KEYS', ''))
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Maintenance Mode Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These configuration options determine the driver used to determine and
|
||||||
|
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||||
|
| allow maintenance mode to be controlled across multiple machines.
|
||||||
|
|
|
||||||
|
| Supported drivers: "file", "cache"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'maintenance' => [
|
||||||
|
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||||
|
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Autoloaded Service Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The service providers listed here will be automatically loaded on the
|
||||||
|
| request to your application. Feel free to add your own services to
|
||||||
|
| this array to grant expanded functionality to your applications.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'providers' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Laravel Framework Service Providers...
|
||||||
|
*/
|
||||||
|
Illuminate\Auth\AuthServiceProvider::class,
|
||||||
|
Illuminate\Broadcasting\BroadcastServiceProvider::class,
|
||||||
|
Illuminate\Bus\BusServiceProvider::class,
|
||||||
|
Illuminate\Cache\CacheServiceProvider::class,
|
||||||
|
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
|
||||||
|
Illuminate\Cookie\CookieServiceProvider::class,
|
||||||
|
Illuminate\Database\DatabaseServiceProvider::class,
|
||||||
|
Illuminate\Encryption\EncryptionServiceProvider::class,
|
||||||
|
Illuminate\Filesystem\FilesystemServiceProvider::class, // 👈 Importante
|
||||||
|
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
|
||||||
|
Illuminate\Hashing\HashServiceProvider::class,
|
||||||
|
Illuminate\Mail\MailServiceProvider::class,
|
||||||
|
Illuminate\Notifications\NotificationServiceProvider::class,
|
||||||
|
Illuminate\Pagination\PaginationServiceProvider::class,
|
||||||
|
Illuminate\Pipeline\PipelineServiceProvider::class,
|
||||||
|
Illuminate\Queue\QueueServiceProvider::class,
|
||||||
|
Illuminate\Redis\RedisServiceProvider::class,
|
||||||
|
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
|
||||||
|
Illuminate\Session\SessionServiceProvider::class,
|
||||||
|
Illuminate\Translation\TranslationServiceProvider::class,
|
||||||
|
Illuminate\Validation\ValidationServiceProvider::class,
|
||||||
|
Illuminate\View\ViewServiceProvider::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Package Service Providers...
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Application Service Providers...
|
||||||
|
*/
|
||||||
|
App\Providers\AppServiceProvider::class,
|
||||||
|
// App\Providers\AuthServiceProvider::class,
|
||||||
|
// App\Providers\BroadcastServiceProvider::class,
|
||||||
|
// App\Providers\EventServiceProvider::class,
|
||||||
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Class Aliases
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This array of class aliases will be registered when this application
|
||||||
|
| is started. However, feel free to register as many as you wish as
|
||||||
|
| the aliases are "lazy" loaded so they don't hinder performance.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'aliases' => [
|
||||||
|
'App' => Illuminate\Support\Facades\App::class,
|
||||||
|
'Arr' => Illuminate\Support\Arr::class,
|
||||||
|
'Artisan' => Illuminate\Support\Facades\Artisan::class,
|
||||||
|
'Auth' => Illuminate\Support\Facades\Auth::class,
|
||||||
|
'Blade' => Illuminate\Support\Facades\Blade::class,
|
||||||
|
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
|
||||||
|
'Bus' => Illuminate\Support\Facades\Bus::class,
|
||||||
|
'Cache' => Illuminate\Support\Facades\Cache::class,
|
||||||
|
'Config' => Illuminate\Support\Facades\Config::class,
|
||||||
|
'Cookie' => Illuminate\Support\Facades\Cookie::class,
|
||||||
|
'Crypt' => Illuminate\Support\Facades\Crypt::class,
|
||||||
|
'DB' => Illuminate\Support\Facades\DB::class,
|
||||||
|
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
|
||||||
|
'Event' => Illuminate\Support\Facades\Event::class,
|
||||||
|
'File' => Illuminate\Support\Facades\File::class,
|
||||||
|
'Gate' => Illuminate\Support\Facades\Gate::class,
|
||||||
|
'Hash' => Illuminate\Support\Facades\Hash::class,
|
||||||
|
'Http' => Illuminate\Support\Facades\Http::class,
|
||||||
|
'Lang' => Illuminate\Support\Facades\Lang::class,
|
||||||
|
'Log' => Illuminate\Support\Facades\Log::class,
|
||||||
|
'Mail' => Illuminate\Support\Facades\Mail::class,
|
||||||
|
'Notification' => Illuminate\Support\Facades\Notification::class,
|
||||||
|
'Password' => Illuminate\Support\Facades\Password::class,
|
||||||
|
'Queue' => Illuminate\Support\Facades\Queue::class,
|
||||||
|
'Redirect' => Illuminate\Support\Facades\Redirect::class,
|
||||||
|
'Request' => Illuminate\Support\Facades\Request::class,
|
||||||
|
'Response' => Illuminate\Support\Facades\Response::class,
|
||||||
|
'Route' => Illuminate\Support\Facades\Route::class,
|
||||||
|
'Schema' => Illuminate\Support\Facades\Schema::class,
|
||||||
|
'Session' => Illuminate\Support\Facades\Session::class,
|
||||||
|
'Storage' => Illuminate\Support\Facades\Storage::class,
|
||||||
|
'Str' => Illuminate\Support\Str::class,
|
||||||
|
'URL' => Illuminate\Support\Facades\URL::class,
|
||||||
|
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||||
|
'View' => Illuminate\Support\Facades\View::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
115
config/auth.php
Normal file
115
config/auth.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Defaults
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option defines the default authentication "guard" and password
|
||||||
|
| reset "broker" for your application. You may change these values
|
||||||
|
| as required, but they're a perfect start for most applications.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'defaults' => [
|
||||||
|
'guard' => env('AUTH_GUARD', 'web'),
|
||||||
|
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Guards
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Next, you may define every authentication guard for your application.
|
||||||
|
| Of course, a great default configuration has been defined for you
|
||||||
|
| which utilizes session storage plus the Eloquent user provider.
|
||||||
|
|
|
||||||
|
| All authentication guards have a user provider, which defines how the
|
||||||
|
| users are actually retrieved out of your database or other storage
|
||||||
|
| system used by the application. Typically, Eloquent is utilized.
|
||||||
|
|
|
||||||
|
| Supported: "session"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'guards' => [
|
||||||
|
'web' => [
|
||||||
|
'driver' => 'session',
|
||||||
|
'provider' => 'users',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| User Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| All authentication guards have a user provider, which defines how the
|
||||||
|
| users are actually retrieved out of your database or other storage
|
||||||
|
| system used by the application. Typically, Eloquent is utilized.
|
||||||
|
|
|
||||||
|
| If you have multiple user tables or models you may configure multiple
|
||||||
|
| providers to represent the model / table. These providers may then
|
||||||
|
| be assigned to any extra authentication guards you have defined.
|
||||||
|
|
|
||||||
|
| Supported: "database", "eloquent"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'providers' => [
|
||||||
|
'users' => [
|
||||||
|
'driver' => 'eloquent',
|
||||||
|
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||||
|
],
|
||||||
|
|
||||||
|
// 'users' => [
|
||||||
|
// 'driver' => 'database',
|
||||||
|
// 'table' => 'users',
|
||||||
|
// ],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Resetting Passwords
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These configuration options specify the behavior of Laravel's password
|
||||||
|
| reset functionality, including the table utilized for token storage
|
||||||
|
| and the user provider that is invoked to actually retrieve users.
|
||||||
|
|
|
||||||
|
| The expiry time is the number of minutes that each reset token will be
|
||||||
|
| considered valid. This security feature keeps tokens short-lived so
|
||||||
|
| they have less time to be guessed. You may change this as needed.
|
||||||
|
|
|
||||||
|
| The throttle setting is the number of seconds a user must wait before
|
||||||
|
| generating more password reset tokens. This prevents the user from
|
||||||
|
| quickly generating a very large amount of password reset tokens.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'passwords' => [
|
||||||
|
'users' => [
|
||||||
|
'provider' => 'users',
|
||||||
|
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||||
|
'expire' => 60,
|
||||||
|
'throttle' => 60,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Confirmation Timeout
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define the amount of seconds before a password confirmation
|
||||||
|
| window expires and users are asked to re-enter their password via the
|
||||||
|
| confirmation screen. By default, the timeout lasts for three hours.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||||
|
|
||||||
|
];
|
||||||
341
config/backup.php
Normal file
341
config/backup.php
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'backup' => [
|
||||||
|
/*
|
||||||
|
* The name of this application. You can use this name to monitor
|
||||||
|
* the backups.
|
||||||
|
*/
|
||||||
|
'name' => 'laravel-backup',
|
||||||
|
|
||||||
|
'source' => [
|
||||||
|
'files' => [
|
||||||
|
/*
|
||||||
|
* The list of directories and files that will be included in the backup.
|
||||||
|
*/
|
||||||
|
'include' => [
|
||||||
|
// base_path(),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These directories and files will be excluded from the backup.
|
||||||
|
*
|
||||||
|
* Directories used by the backup process will automatically be excluded.
|
||||||
|
*/
|
||||||
|
'exclude' => [
|
||||||
|
// base_path('vendor'),
|
||||||
|
// base_path('node_modules'),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determines if symlinks should be followed.
|
||||||
|
*/
|
||||||
|
'follow_links' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determines if it should avoid unreadable folders.
|
||||||
|
*/
|
||||||
|
'ignore_unreadable_directories' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This path is used to make directories in resulting zip-file relative
|
||||||
|
* Set to `null` to include complete absolute path
|
||||||
|
* Example: base_path()
|
||||||
|
*/
|
||||||
|
'relative_path' => null,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The names of the connections to the databases that should be backed up
|
||||||
|
* MySQL, PostgreSQL, SQLite and Mongo databases are supported.
|
||||||
|
*
|
||||||
|
* The content of the database dump may be customized for each connection
|
||||||
|
* by adding a 'dump' key to the connection settings in config/database.php.
|
||||||
|
* E.g.
|
||||||
|
* 'mysql' => [
|
||||||
|
* ...
|
||||||
|
* 'dump' => [
|
||||||
|
* 'excludeTables' => [
|
||||||
|
* 'table_to_exclude_from_backup',
|
||||||
|
* 'another_table_to_exclude'
|
||||||
|
* ]
|
||||||
|
* ],
|
||||||
|
* ],
|
||||||
|
*
|
||||||
|
* If you are using only InnoDB tables on a MySQL server, you can
|
||||||
|
* also supply the useSingleTransaction option to avoid table locking.
|
||||||
|
*
|
||||||
|
* E.g.
|
||||||
|
* 'mysql' => [
|
||||||
|
* ...
|
||||||
|
* 'dump' => [
|
||||||
|
* 'useSingleTransaction' => true,
|
||||||
|
* ],
|
||||||
|
* ],
|
||||||
|
*
|
||||||
|
* For a complete list of available customization options, see https://github.com/spatie/db-dumper
|
||||||
|
*/
|
||||||
|
'databases' => [
|
||||||
|
env('DB_CONNECTION', 'mysql'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The database dump can be compressed to decrease disk space usage.
|
||||||
|
*
|
||||||
|
* Out of the box Laravel-backup supplies
|
||||||
|
* Spatie\DbDumper\Compressors\GzipCompressor::class.
|
||||||
|
*
|
||||||
|
* You can also create custom compressor. More info on that here:
|
||||||
|
* https://github.com/spatie/db-dumper#using-compression
|
||||||
|
*
|
||||||
|
* If you do not want any compressor at all, set it to null.
|
||||||
|
*/
|
||||||
|
'database_dump_compressor' => Spatie\DbDumper\Compressors\GzipCompressor::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If specified, the database dumped file name will contain a timestamp (e.g.: 'Y-m-d-H-i-s').
|
||||||
|
*/
|
||||||
|
'database_dump_file_timestamp_format' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The base of the dump filename, either 'database' or 'connection'
|
||||||
|
*
|
||||||
|
* If 'database' (default), the dumped filename will contain the database name.
|
||||||
|
* If 'connection', the dumped filename will contain the connection name.
|
||||||
|
*/
|
||||||
|
'database_dump_filename_base' => 'database',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The file extension used for the database dump files.
|
||||||
|
*
|
||||||
|
* If not specified, the file extension will be .archive for MongoDB and .sql for all other databases
|
||||||
|
* The file extension should be specified without a leading .
|
||||||
|
*/
|
||||||
|
'database_dump_file_extension' => '',
|
||||||
|
|
||||||
|
'destination' => [
|
||||||
|
/*
|
||||||
|
* The compression algorithm to be used for creating the zip archive.
|
||||||
|
*
|
||||||
|
* If backing up only database, you may choose gzip compression for db dump and no compression at zip.
|
||||||
|
*
|
||||||
|
* Some common algorithms are listed below:
|
||||||
|
* ZipArchive::CM_STORE (no compression at all; set 0 as compression level)
|
||||||
|
* ZipArchive::CM_DEFAULT
|
||||||
|
* ZipArchive::CM_DEFLATE
|
||||||
|
* ZipArchive::CM_BZIP2
|
||||||
|
* ZipArchive::CM_XZ
|
||||||
|
*
|
||||||
|
* For more check https://www.php.net/manual/zip.constants.php and confirm it's supported by your system.
|
||||||
|
*/
|
||||||
|
'compression_method' => ZipArchive::CM_DEFAULT,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The compression level corresponding to the used algorithm; an integer between 0 and 9.
|
||||||
|
*
|
||||||
|
* Check supported levels for the chosen algorithm, usually 1 means the fastest and weakest compression,
|
||||||
|
* while 9 the slowest and strongest one.
|
||||||
|
*
|
||||||
|
* Setting of 0 for some algorithms may switch to the strongest compression.
|
||||||
|
*/
|
||||||
|
'compression_level' => 9,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The filename prefix used for the backup zip file.
|
||||||
|
*/
|
||||||
|
'filename_prefix' => '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The disk names on which the backups will be stored.
|
||||||
|
*/
|
||||||
|
'disks' => [
|
||||||
|
env('FILESYSTEM_DISK', 'local'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The directory where the temporary files will be stored.
|
||||||
|
*/
|
||||||
|
'temporary_directory' => storage_path('app/backup-temp'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The password to be used for archive encryption.
|
||||||
|
* Set to `null` to disable encryption.
|
||||||
|
*/
|
||||||
|
'password' => env('BACKUP_ARCHIVE_PASSWORD'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The encryption algorithm to be used for archive encryption.
|
||||||
|
* You can set it to `null` or `false` to disable encryption.
|
||||||
|
*
|
||||||
|
* When set to 'default', we'll use ZipArchive::EM_AES_256 if it is
|
||||||
|
* available on your system.
|
||||||
|
*/
|
||||||
|
'encryption' => 'default',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The number of attempts, in case the backup command encounters an exception
|
||||||
|
*/
|
||||||
|
'tries' => 1,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The number of seconds to wait before attempting a new backup if the previous try failed
|
||||||
|
* Set to `0` for none
|
||||||
|
*/
|
||||||
|
'retry_delay' => 0,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'.
|
||||||
|
* For Slack you need to install laravel/slack-notification-channel.
|
||||||
|
*
|
||||||
|
* You can also use your own notification classes, just make sure the class is named after one of
|
||||||
|
* the `Spatie\Backup\Notifications\Notifications` classes.
|
||||||
|
*/
|
||||||
|
'notifications' => [
|
||||||
|
'notifications' => [
|
||||||
|
\Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification::class => ['mail'],
|
||||||
|
\Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFoundNotification::class => ['mail'],
|
||||||
|
\Spatie\Backup\Notifications\Notifications\CleanupHasFailedNotification::class => ['mail'],
|
||||||
|
\Spatie\Backup\Notifications\Notifications\BackupWasSuccessfulNotification::class => ['mail'],
|
||||||
|
\Spatie\Backup\Notifications\Notifications\HealthyBackupWasFoundNotification::class => ['mail'],
|
||||||
|
\Spatie\Backup\Notifications\Notifications\CleanupWasSuccessfulNotification::class => ['mail'],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here you can specify the notifiable to which the notifications should be sent. The default
|
||||||
|
* notifiable will use the variables specified in this config file.
|
||||||
|
*/
|
||||||
|
'notifiable' => \Spatie\Backup\Notifications\Notifiable::class,
|
||||||
|
|
||||||
|
'mail' => [
|
||||||
|
'to' => ['luismedon@gmail.com'],
|
||||||
|
|
||||||
|
'from' => [
|
||||||
|
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||||
|
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'slack' => [
|
||||||
|
'webhook_url' => '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If this is set to null the default channel of the webhook will be used.
|
||||||
|
*/
|
||||||
|
'channel' => null,
|
||||||
|
|
||||||
|
'username' => null,
|
||||||
|
|
||||||
|
'icon' => null,
|
||||||
|
],
|
||||||
|
|
||||||
|
'discord' => [
|
||||||
|
'webhook_url' => '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If this is an empty string, the name field on the webhook will be used.
|
||||||
|
*/
|
||||||
|
'username' => '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If this is an empty string, the avatar on the webhook will be used.
|
||||||
|
*/
|
||||||
|
'avatar_url' => '',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here you can specify which backups should be monitored.
|
||||||
|
* If a backup does not meet the specified requirements the
|
||||||
|
* UnHealthyBackupWasFound event will be fired.
|
||||||
|
*/
|
||||||
|
'monitor_backups' => [
|
||||||
|
[
|
||||||
|
'name' => 'laravel-backup',
|
||||||
|
'disks' => [env('FILESYSTEM_DISK', 'local')],
|
||||||
|
'health_checks' => [
|
||||||
|
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1,
|
||||||
|
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
[
|
||||||
|
'name' => 'name of the second app',
|
||||||
|
'disks' => ['local', 's3'],
|
||||||
|
'health_checks' => [
|
||||||
|
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1,
|
||||||
|
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
|
||||||
|
'cleanup' => [
|
||||||
|
/*
|
||||||
|
* The strategy that will be used to cleanup old backups. The default strategy
|
||||||
|
* will keep all backups for a certain amount of days. After that period only
|
||||||
|
* a daily backup will be kept. After that period only weekly backups will
|
||||||
|
* be kept and so on.
|
||||||
|
*
|
||||||
|
* No matter how you configure it the default strategy will never
|
||||||
|
* delete the newest backup.
|
||||||
|
*/
|
||||||
|
'strategy' => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,
|
||||||
|
|
||||||
|
'default_strategy' => [
|
||||||
|
/*
|
||||||
|
* The number of days for which backups must be kept.
|
||||||
|
*/
|
||||||
|
'keep_all_backups_for_days' => 0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After the "keep_all_backups_for_days" period is over, the most recent backup
|
||||||
|
* of that day will be kept. Older backups within the same day will be removed.
|
||||||
|
* If you create backups only once a day, no backups will be removed yet.
|
||||||
|
*/
|
||||||
|
'keep_daily_backups_for_days' => 5,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After the "keep_daily_backups_for_days" period is over, the most recent backup
|
||||||
|
* of that week will be kept. Older backups within the same week will be removed.
|
||||||
|
* If you create backups only once a week, no backups will be removed yet.
|
||||||
|
*/
|
||||||
|
'keep_weekly_backups_for_weeks' => 0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After the "keep_weekly_backups_for_weeks" period is over, the most recent backup
|
||||||
|
* of that month will be kept. Older backups within the same month will be removed.
|
||||||
|
*/
|
||||||
|
'keep_monthly_backups_for_months' => 0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After the "keep_monthly_backups_for_months" period is over, the most recent backup
|
||||||
|
* of that year will be kept. Older backups within the same year will be removed.
|
||||||
|
*/
|
||||||
|
'keep_yearly_backups_for_years' => 0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After cleaning up the backups remove the oldest backup until
|
||||||
|
* this amount of megabytes has been reached.
|
||||||
|
* Set null for unlimited size.
|
||||||
|
*/
|
||||||
|
'delete_oldest_backups_when_using_more_megabytes_than' => null,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The number of attempts, in case the cleanup command encounters an exception
|
||||||
|
*/
|
||||||
|
'tries' => 1,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The number of seconds to wait before attempting a new cleanup if the previous try failed
|
||||||
|
* Set to `0` for none
|
||||||
|
*/
|
||||||
|
'retry_delay' => 0,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
108
config/cache.php
Normal file
108
config/cache.php
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Cache Store
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default cache store that will be used by the
|
||||||
|
| framework. This connection is utilized if another isn't explicitly
|
||||||
|
| specified when running a cache operation inside the application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('CACHE_DRIVER', 'database'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Stores
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define all of the cache "stores" for your application as
|
||||||
|
| well as their drivers. You may even define multiple stores for the
|
||||||
|
| same cache driver to group types of items stored in your caches.
|
||||||
|
|
|
||||||
|
| Supported drivers: "array", "database", "file", "memcached",
|
||||||
|
| "redis", "dynamodb", "octane", "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'stores' => [
|
||||||
|
|
||||||
|
'array' => [
|
||||||
|
'driver' => 'array',
|
||||||
|
'serialize' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'database' => [
|
||||||
|
'driver' => 'database',
|
||||||
|
'connection' => env('DB_CACHE_CONNECTION'),
|
||||||
|
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||||
|
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||||
|
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'file' => [
|
||||||
|
'driver' => 'file',
|
||||||
|
'path' => storage_path('framework/cache/data'),
|
||||||
|
'lock_path' => storage_path('framework/cache/data'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'memcached' => [
|
||||||
|
'driver' => 'memcached',
|
||||||
|
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||||
|
'sasl' => [
|
||||||
|
env('MEMCACHED_USERNAME'),
|
||||||
|
env('MEMCACHED_PASSWORD'),
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||||
|
],
|
||||||
|
'servers' => [
|
||||||
|
[
|
||||||
|
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('MEMCACHED_PORT', 11211),
|
||||||
|
'weight' => 100,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'driver' => 'redis',
|
||||||
|
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||||
|
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'dynamodb' => [
|
||||||
|
'driver' => 'dynamodb',
|
||||||
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
|
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||||
|
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'octane' => [
|
||||||
|
'driver' => 'octane',
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Key Prefix
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
|
||||||
|
| stores, there might be other applications using the same cache. For
|
||||||
|
| that reason, you may prefix every cache key to avoid collisions.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'prefix' => env('CACHE_PREFIX'),
|
||||||
|
|
||||||
|
];
|
||||||
174
config/database.php
Normal file
174
config/database.php
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Database Connection Name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify which of the database connections below you wish
|
||||||
|
| to use as your default connection for database operations. This is
|
||||||
|
| the connection which will be utilized unless another connection
|
||||||
|
| is explicitly specified when you execute a query / statement.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Database Connections
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Below are all of the database connections defined for your application.
|
||||||
|
| An example configuration is provided for each database system which
|
||||||
|
| is supported by Laravel. You're free to add / remove connections.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'connections' => [
|
||||||
|
|
||||||
|
'sqlite' => [
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||||
|
'prefix' => '',
|
||||||
|
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||||
|
'busy_timeout' => null,
|
||||||
|
'journal_mode' => null,
|
||||||
|
'synchronous' => null,
|
||||||
|
],
|
||||||
|
|
||||||
|
'mysql' => [
|
||||||
|
'driver' => 'mysql',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('DB_PORT', '3306'),
|
||||||
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'unix_socket' => env('DB_SOCKET', ''),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||||
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'strict' => true,
|
||||||
|
'engine' => null,
|
||||||
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
|
]) : [],
|
||||||
|
],
|
||||||
|
|
||||||
|
'mariadb' => [
|
||||||
|
'driver' => 'mariadb',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('DB_PORT', '3306'),
|
||||||
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'unix_socket' => env('DB_SOCKET', ''),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||||
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'strict' => true,
|
||||||
|
'engine' => null,
|
||||||
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
|
]) : [],
|
||||||
|
],
|
||||||
|
|
||||||
|
'pgsql' => [
|
||||||
|
'driver' => 'pgsql',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('DB_PORT', '5432'),
|
||||||
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'search_path' => 'public',
|
||||||
|
'sslmode' => 'prefer',
|
||||||
|
],
|
||||||
|
|
||||||
|
'sqlsrv' => [
|
||||||
|
'driver' => 'sqlsrv',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'host' => env('DB_HOST', 'localhost'),
|
||||||
|
'port' => env('DB_PORT', '1433'),
|
||||||
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||||
|
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Migration Repository Table
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This table keeps track of all the migrations that have already run for
|
||||||
|
| your application. Using this information, we can determine which of
|
||||||
|
| the migrations on disk haven't actually been run on the database.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'migrations' => [
|
||||||
|
'table' => 'migrations',
|
||||||
|
'update_date_on_publish' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Redis Databases
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Redis is an open source, fast, and advanced key-value store that also
|
||||||
|
| provides a richer body of commands than a typical key-value system
|
||||||
|
| such as Memcached. You may define your connection settings here.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
|
||||||
|
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||||
|
|
||||||
|
'options' => [
|
||||||
|
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||||
|
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
||||||
|
'persistent' => env('REDIS_PERSISTENT', false),
|
||||||
|
],
|
||||||
|
|
||||||
|
'default' => [
|
||||||
|
'url' => env('REDIS_URL'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'username' => env('REDIS_USERNAME'),
|
||||||
|
'password' => env('REDIS_PASSWORD'),
|
||||||
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => env('REDIS_DB', '0'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'cache' => [
|
||||||
|
'url' => env('REDIS_URL'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'username' => env('REDIS_USERNAME'),
|
||||||
|
'password' => env('REDIS_PASSWORD'),
|
||||||
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => env('REDIS_CACHE_DB', '1'),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
79
config/filesystems.php
Normal file
79
config/filesystems.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Filesystem Disk
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify the default filesystem disk that should be used
|
||||||
|
| by the framework. The "local" disk, as well as a variety of cloud
|
||||||
|
| based disks are available to your application for file storage.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Filesystem Disks
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Below you may configure as many filesystem disks as necessary, and you
|
||||||
|
| may even configure multiple disks for the same driver. Examples for
|
||||||
|
| most supported storage drivers are configured here for reference.
|
||||||
|
|
|
||||||
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'disks' => [
|
||||||
|
|
||||||
|
'local' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => storage_path('app/private'),
|
||||||
|
'serve' => true,
|
||||||
|
'throw' => false,
|
||||||
|
'report' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'public' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => storage_path('app/public'),
|
||||||
|
'url' => env('APP_URL').'/storage',
|
||||||
|
'visibility' => 'public',
|
||||||
|
'throw' => false,
|
||||||
|
'report' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
's3' => [
|
||||||
|
'driver' => 's3',
|
||||||
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('AWS_DEFAULT_REGION'),
|
||||||
|
'bucket' => env('AWS_BUCKET'),
|
||||||
|
'url' => env('AWS_URL'),
|
||||||
|
'endpoint' => env('AWS_ENDPOINT'),
|
||||||
|
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', true),
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Symbolic Links
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure the symbolic links that will be created when the
|
||||||
|
| `storage:link` Artisan command is executed. The array keys should be
|
||||||
|
| the locations of the links and the values should be their targets.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'links' => [
|
||||||
|
public_path('storage') => storage_path('app/public'),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
132
config/logging.php
Normal file
132
config/logging.php
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Monolog\Handler\NullHandler;
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
|
use Monolog\Handler\SyslogUdpHandler;
|
||||||
|
use Monolog\Processor\PsrLogMessageProcessor;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Log Channel
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option defines the default log channel that is utilized to write
|
||||||
|
| messages to your logs. The value provided here should match one of
|
||||||
|
| the channels present in the list of "channels" configured below.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('LOG_CHANNEL', 'stack'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Deprecations Log Channel
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the log channel that should be used to log warnings
|
||||||
|
| regarding deprecated PHP and library features. This allows you to get
|
||||||
|
| your application ready for upcoming major versions of dependencies.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'deprecations' => [
|
||||||
|
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||||
|
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Log Channels
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure the log channels for your application. Laravel
|
||||||
|
| utilizes the Monolog PHP logging library, which includes a variety
|
||||||
|
| of powerful log handlers and formatters that you're free to use.
|
||||||
|
|
|
||||||
|
| Available drivers: "single", "daily", "slack", "syslog",
|
||||||
|
| "errorlog", "monolog", "custom", "stack"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'channels' => [
|
||||||
|
|
||||||
|
'stack' => [
|
||||||
|
'driver' => 'stack',
|
||||||
|
'channels' => explode(',', env('LOG_STACK', 'single')),
|
||||||
|
'ignore_exceptions' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'single' => [
|
||||||
|
'driver' => 'single',
|
||||||
|
'path' => storage_path('logs/laravel.log'),
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'daily' => [
|
||||||
|
'driver' => 'daily',
|
||||||
|
'path' => storage_path('logs/laravel.log'),
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'days' => env('LOG_DAILY_DAYS', 14),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'slack' => [
|
||||||
|
'driver' => 'slack',
|
||||||
|
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||||
|
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
|
||||||
|
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
|
||||||
|
'level' => env('LOG_LEVEL', 'critical'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'papertrail' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||||
|
'handler_with' => [
|
||||||
|
'host' => env('PAPERTRAIL_URL'),
|
||||||
|
'port' => env('PAPERTRAIL_PORT'),
|
||||||
|
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||||
|
],
|
||||||
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
|
],
|
||||||
|
|
||||||
|
'stderr' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'handler' => StreamHandler::class,
|
||||||
|
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||||
|
'with' => [
|
||||||
|
'stream' => 'php://stderr',
|
||||||
|
],
|
||||||
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
|
],
|
||||||
|
|
||||||
|
'syslog' => [
|
||||||
|
'driver' => 'syslog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'errorlog' => [
|
||||||
|
'driver' => 'errorlog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'null' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'handler' => NullHandler::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'emergency' => [
|
||||||
|
'path' => storage_path('logs/laravel.log'),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user