From e975497b1ce28773425f4afb1c53344e1daa6b5d Mon Sep 17 00:00:00 2001 From: Vincent Vanwaelscappel Date: Tue, 8 Jul 2025 19:30:17 +0200 Subject: [PATCH] wait #7607 @12 --- .../Admin/ExtranetTotalsController.php | 23 ++ app/Jobs/ProcessTotals.php | 128 ++++++++++- app/helpers.php | 23 +- resources/views/extranet/totals.blade.php | 214 ++++++++++++++++++ .../base/inc/sidebar_content.blade.php | 12 + routes/web.php | 1 + 6 files changed, 395 insertions(+), 6 deletions(-) create mode 100644 app/Http/Controllers/Admin/ExtranetTotalsController.php create mode 100644 resources/views/extranet/totals.blade.php diff --git a/app/Http/Controllers/Admin/ExtranetTotalsController.php b/app/Http/Controllers/Admin/ExtranetTotalsController.php new file mode 100644 index 000000000..023e5e93b --- /dev/null +++ b/app/Http/Controllers/Admin/ExtranetTotalsController.php @@ -0,0 +1,23 @@ + $amount) { + $chartRevenue[] = ['year' => $year, 'amount' => $amount]; + $chartRevenuePrevision[] = ['year', $year, 'amount' => $currentYear == $year ? $data['pendingProjects'] : ($year == $currentYear - 1 ? 0 : ("NaN"))]; + } + + return view('extranet.totals', ['currentYear'=>$currentYear,'chart_revenue' => $chartRevenue, 'chart_revenue_prevision' => $chartRevenuePrevision, 'data' => $data]); + } +} diff --git a/app/Jobs/ProcessTotals.php b/app/Jobs/ProcessTotals.php index 97de642db..2782e2803 100644 --- a/app/Jobs/ProcessTotals.php +++ b/app/Jobs/ProcessTotals.php @@ -7,12 +7,11 @@ use App\Models\FluidbookQuote; use App\Models\User; use Cubedesigners\UserDatabase\Permissions; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; class ProcessTotals extends Base { - - protected $projects = []; protected $invoices = []; protected $invoicesByProject = []; @@ -23,6 +22,13 @@ class ProcessTotals extends Base protected $companyYears = []; protected $unpaidYears = []; protected $unpaid = []; + protected $totalYears = []; + protected $totalMonths = []; + protected $totalQuarters = []; + protected $projects_budget = []; + protected $unpaidTotals = []; + protected $pendingProjects = 0; + protected $totalCategories = []; const firstYear = 2006; protected $lastProject = []; @@ -31,22 +37,78 @@ class ProcessTotals extends Base public function handle() { $this->processInvoices(); + $this->processProjects(); $this->processCompanies(); $this->processFluidbookCounts(); Artisan::command('ws:precache', function () { }); + + cache()->forever('extranet_totals', + [ + 'pendingProjects' => $this->pendingProjects, + 'years' => $this->totalYears, + 'months' => $this->totalMonths, + 'quarters' => $this->totalQuarters, + 'currentYear' => $this->totalYears[date('Y')], + 'currentYearProjection' => $this->totalYears[date('Y')] + $this->pendingProjects, + 'unpaid' => $this->unpaidTotals, + 'categories' => $this->totalCategories, + ] + ); } protected function processInvoices() { + $d30 = time() - (30 * 3600 * 24); + $d90 = time() - (90 * 3600 * 24); + $d365 = time() - (365 * 3600 * 24); + + $this->unpaidTotals = ['all' => 0, 'd30' => 0, 'd90' => 0, 'd365' => 0]; + foreach (DB::table(self::$_wstable . '.factures')->whereIn('status', [1, 2])->get() as $e) { + $year = date('Y', $e->date_creation); + $month = date('m', $e->date_creation); + $quarter = ceil($month / 3); $this->invoices[$e->facture_id] = - ['status' => $e->status, 'amount' => $e->total_ht, 'project' => $e->projet, 'year' => date('Y', $e->date_creation), 'paid' => $e->status == 2]; + ['status' => $e->status, 'amount' => $e->total_ht, 'project' => $e->projet, 'year' => $year, 'month' => $year . '-' . $month, 'quarter' => $year . '-Q' . $quarter, 'paid' => $e->status == 2]; + + if ($e->status == 1) { + $this->unpaidTotals['all'] += $e->total_ht; + if ($e->date_creation < $d30) { + $this->unpaidTotals['d30'] += $e->total_ht; + if ($e->date_creation < $d90) { + $this->unpaidTotals['d90'] += $e->total_ht; + if ($e->date_creation < $d365) { + $this->unpaidTotals['d365'] += $e->total_ht; + } + } + } + } + + if (!isset($this->totalYears[$year])) { + $this->totalYears[$year] = 0; + } + $this->totalYears[$year] += $e->total_ht; + $m = $year . '-' . $month; + if (!isset($this->totalMonths[$m])) { + $this->totalMonths[$m] = 0; + } + $this->totalMonths[$m] += $e->total_ht; + + $q = $year . '-Q' . $quarter; + if (!isset($this->totalQuarters[$q])) { + $this->totalQuarters[$q] = 0; + } + $this->totalQuarters[$q] += $e->total_ht; if (!isset($this->invoicesByProject[$e->projet])) { $this->invoicesByProject[$e->projet] = []; } $this->invoicesByProject[$e->projet][] = $e->facture_id; } + + ksort($this->totalYears); + ksort($this->totalMonths); + ksort($this->totalQuarters); } @@ -64,7 +126,6 @@ class ProcessTotals extends Base } foreach (DB::table(self::$_wstable . '.projets')->get() as $e) { - if (!isset($this->companyOfUser[$e->client])) { continue; } @@ -150,4 +211,63 @@ class ProcessTotals extends Base } } + protected function processProjects() + { + $currentYear = date('Y'); + + $projectsYears = []; + foreach (DB::table(self::$_wstable . '.projets')->get() as $e) { + $projectsYears[$e->projet_id] = date('Y', $e->date_creation); + } + + foreach (DB::table(self::$_wstable . '.taches')->get() as $e) { + if (!isset($this->projects_budget[$e->projet])) { + $this->projects_budget[$e->projet] = 0; + } + if (isset($projectsYears[$e->projet])) { + $y = $projectsYears[$e->projet]; + if (!isset($this->totalCategories[$e->categorie])) { + $this->totalCategories[$e->categorie] = ['total' => 0, 'ratio' => 0, 'years' => []]; + } + if (!isset($this->totalCategories[$e->categorie]['years'][$y])) { + $this->totalCategories[$e->categorie]['years'][$y] = ['amount' => 0, 'ratio' => 0]; + } + $this->totalCategories[$e->categorie]['years'][$y]['amount'] += $e->budget; + $this->totalCategories[$e->categorie]['total'] += $e->budget; + } + + + $this->projects_budget[$e->projet] += $e->budget; + } + + foreach ($this->totalCategories as $categorie => $c) { + if (!$c['total']) { + continue; + } + foreach ($c['years'] as $year => $cc) { + $yearTotal = $this->totalYears[$year]; + if ($year == $currentYear) { + $yearTotal += $this->pendingProjects; + } + $this->totalCategories[$categorie]['years'][$year]['ratio'] = $cc['amount'] / $yearTotal; + } + } + + // projects older than 18 month won't be invoices I suppose + $limit = time() - (3600 * 24 * 365 * 1.5); + foreach (DB::table(self::$_wstable . '.projets')->where('date_creation', '>', $limit)->where('status', 0)->get() as $e) { + $alreadyInvoiced = 0; + if (isset($this->invoicesByProject[$e->projet_id])) { + foreach ($this->invoicesByProject[$e->projet_id] as $invoice_id) { + $alreadyInvoiced += $this->invoices[$invoice_id]['amount']; + } + } + + $this->pendingProjects += max(0, ($this->projects_budget[$e->projet_id] ?? 0) - $alreadyInvoiced); + } + + ksort($this->totalCategories); + } + + } diff --git a/app/helpers.php b/app/helpers.php index 89d02a397..53bd158f1 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -23,7 +23,6 @@ if (!function_exists('dddump')) { } - if (!function_exists('us_path')) { function us_path($path = '') { @@ -32,8 +31,28 @@ if (!function_exists('us_path')) { } if (!function_exists('us_protected_path')) { - function us_protected_path($path='') + function us_protected_path($path = '') { return us_path('protected/' . $path); } } + +if (!function_exists('format_ke')) { + function format_ke($number) + { + if (!$number) { + return '-'; + } + return round($number / 1000) . ' K€'; + } +} + +if (!function_exists('format_pct')) { + function format_pct($number) + { + if (!$number) { + return ''; + } + return number_format($number * 100, 2, '.', ',') . ' %'; + } +} diff --git a/resources/views/extranet/totals.blade.php b/resources/views/extranet/totals.blade.php new file mode 100644 index 000000000..3ff814ba8 --- /dev/null +++ b/resources/views/extranet/totals.blade.php @@ -0,0 +1,214 @@ +{{-- __('!! Extranet') --}} +@extends(backpack_view('blank')) + +@php + $categories = array(0 => __('Non défini'), 1 => __('Gestion de projet'), + 2 => __('Design Web'), 3 => __('Design Industriel'), 4 => __('Print'), + 5 => __('Newsletter'), 6 => __('Développement PHP'), 7 => __('Développement Flash'), + 8 => __('Fluidbook'), 9 => __('Formation'), 10 => __('Administratif'), 11 => __('Divers'), + 12 => __('Intégration HTML'), 13 => __('Motion design'), 14 => __('Design graphique'), + 15 => __('Bandeaux de pub'), 16 => __('Applications mobiles'), 17 => __('Prise de vue photo/vidéo'), 18 => __('Hébergement')); +@endphp + +@section('content') + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

{{__('Détails de l\'année :year',['year'=>date('Y')])}}

+
{{__('Impayés')}}{{format_ke($data['unpaid']['all'])}}
{{__('Projets en cours')}}{{format_ke($data['pendingProjects'])}}{{__('Impayés de plus de 30 jours')}}{{format_ke($data['unpaid']['d30'])}}
{{__('Chiffre d\'affaire')}}{{format_ke($data['currentYear'])}}{{__('Impayés de plus de 90 jours')}}{{format_ke($data['unpaid']['d90'])}}
{{__('Prévision de chiffre d\'affaire')}}{{format_ke($data['currentYearProjection'])}}{{__('Impayés de plus d\'un an')}}{{format_ke($data['unpaid']['d365'])}}
+ +

{{__('Évolution du chiffre d\'affaire')}}

+
+ +
+ + + + + + + + + + + + + + + @for($y=$currentYear;$y>=2009;$y--) + + + + @for($i=1;$i<=4;$i++) + + @endfor + + + + @endfor + +

{{__('Chiffre d\'affaire trimestriel')}}

+
T1T2T3T4{{__('Total')}}
{{$y}}{{format_ke($data['quarters'][$y.'-Q'.$i]??0)}}{{format_ke($data['years'][$y])}}
+ +

{{__('Chiffre d\'affaire mensuel')}}

+ + + + + + + + + + + + + + + + + + + + + + @for($y=$currentYear;$y>=2009;$y--) + + + + @for($i=1;$i<=12;$i++) + + @endfor + + + @endfor + +
{{__('Année')}}{{__('Janvier')}}{{__('Février')}}{{__('Mars')}}{{__('Avril')}}{{__('Mai')}}{{__('Juin')}}{{__('Juillet')}}{{__('Août')}}{{__('Septembre')}}{{__('Octobre')}}{{__('Novembre')}}{{__('Décembre')}}{{__('Total')}}
{{$y}}{{format_ke($data['months'][$y.'-'.($i>=10?$i:'0'.$i)]??0)}}{{format_ke($data['years'][$y])}}
+ +

{{__('Répartition du chiffre d\'affaire par catégorie')}}

+ + + + + + @for($y=$currentYear-11;$y<=$currentYear;$y++) + + @endfor + + + + + @foreach($categories as $c=>$label) + + + + @for($y=$currentYear-11;$y<=$currentYear;$y++) + + @endfor + + + + @endforeach + +
{{__('Catégorie')}}{{$y}}{{__('Total')}}
{{$label}}{{format_ke($data['categories'][$c]['years'][$y]['amount']??0)}}
{{format_pct($data['categories'][$c]['years'][$y]['ratio']??0)}}
{{format_ke($data['categories'][$c]['total']??0)}}
+@endsection + +@push('after_scripts') + {{-- Charting library --}} + + +@endpush diff --git a/resources/views/vendor/backpack/base/inc/sidebar_content.blade.php b/resources/views/vendor/backpack/base/inc/sidebar_content.blade.php index 68ed51fc1..1797ffbe5 100644 --- a/resources/views/vendor/backpack/base/inc/sidebar_content.blade.php +++ b/resources/views/vendor/backpack/base/inc/sidebar_content.blade.php @@ -195,6 +195,18 @@ @endcan +@canany(['extranet:totals']) +
  • {{__('Extranet')}} + +
  • +@endcan @canany(['team-leave:read','team-overtime:read','extranet:manage_emails'])
  • where(['path' => '.*']); Route::delete('tasks/notification/{id}', 'TasksController@deleteNotification'); + Route::get('extranet/totals', 'ExtranetTotalsController@index'); }); Route::group([ -- 2.39.5