Spamworldpro Mini Shell
Spamworldpro


Server : Apache
System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64
User : corals ( 1002)
PHP Version : 7.4.33
Disable Function : exec,passthru,shell_exec,system
Directory :  /home/corals/dceprojects.corals.io/Corals/modules/Timesheet/Classes/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/corals/dceprojects.corals.io/Corals/modules/Timesheet/Classes/Report.php
<?php

namespace Corals\Modules\Timesheet\Classes;

use Carbon\Carbon;
use Corals\Foundation\Classes\ExcelWriter;
use Corals\Modules\Timesheet\Models\Entry;
use Corals\User\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;

class Report
{
    protected $userId;
    protected $startDate;
    protected $jobId;
    protected $endDate;
    protected $departmentCode;
    protected $reportBy;
    protected $overUsageJob;

    public function __construct($userId = null, $jobId = null, $startDate = null, $endDate = null, $departmentCode = null, $reportBy = 'employees', $overUsageJob = false)
    {
        $this->userId = $userId;
        $this->jobId = $jobId;
        $this->startDate = Carbon::parse($startDate);
        $this->endDate = Carbon::parse($endDate);
        $this->reportBy = $reportBy;
        $this->departmentCode = $departmentCode;
        $this->overUsageJob = !!$overUsageJob;
    }

    public function generateReport()
    {
        $entryQB = $this->buildQuery();
        return $this->extractDataForReport($entryQB);
    }

    protected function buildQuery()
    {
        return Entry::query()
            ->when($this->reportBy == 'employees', function ($query) {
                $query->with('user')
                    ->join('users', 'ts_entries.user_id', '=', 'users.id');
            })
            ->when(in_array($this->reportBy, ['departments', 'jobs']) || $this->departmentCode, function ($query) {
                $query->join('utility_list_of_values', 'ts_entries.department_code', '=', 'utility_list_of_values.code');
            })
            ->when(in_array($this->reportBy, ['jobs', 'employees']), function ($query) {
                $query->join('ts_jobs', 'ts_entries.entrieable_id', '=', 'ts_jobs.id');
            })
            ->when($this->userId, function ($query) {
                $query->where('ts_entries.user_id', $this->userId);
            })
            ->when($this->jobId, function ($query) {
                $query->where('ts_entries.entrieable_id', $this->jobId);
            })
            ->when($this->departmentCode, function ($query) {
                $query->where('utility_list_of_values.code', $this->departmentCode);
            })
            ->where(function ($query) {
                $query->whereDate("ts_entries.spent_at", '>=', $this->startDate)
                    ->whereDate("ts_entries.spent_at", '<=', $this->endDate);
            });
    }

    public function exports()
    {
        $fileName = $this->createFileName();

        $reportExcel = ExcelWriter::create($fileName);

        $reportRecords = $this->generateReport();

        foreach ($reportRecords['records'] as $record) {
            $this->addRawToExcel($reportExcel, $record);
        }

        $reportExcel->close();

        return response()->download($fileName)->deleteFileAfterSend();
    }

    protected function createFileName(): string
    {
        $path = config('app.export_excel_base_path');
        $publicPath = storage_path($path);

        if (!File::exists($publicPath)) {
            File::makeDirectory($publicPath, 0755, true);
        }

        return sprintf(
            '%s/%s_q%s_%s.xlsx',
            $publicPath,
            Str::slug("Report"),
            uniqid(),
            now()->format('Y_dM_H_i')
        );
    }

    private function addRawToExcel(ExcelWriter $reportExcel, $record)
    {
        $arrayColumns = collect($record)->filter(function ($value) {
            return is_array($value);
        });

        $nonArrayColumns = collect($record)->filter(function ($value) {
            return !is_array($value);
        });

        $combinations = $this->generateCombinations($arrayColumns);

        foreach ($combinations as $combination) {
            $row = $nonArrayColumns->merge($combination->first())->mapWithKeys(function ($value, $key) {
                return [Str::title(str_replace('_', ' ', $key)) => $this->sanitizeValue($value)];
            })->toArray();

            $reportExcel->addRow($row);
        }
    }

    private function generateCombinations($arrayColumns)
    {
        $arrays = $arrayColumns->values()->toArray();
        $keys = $arrayColumns->keys()->toArray();

        $combinations = $this->cartesianProduct($arrays);

        return collect($combinations)->map(function ($combination) use ($keys) {
            return collect($combination)->mapWithKeys(function ($value, $index) use ($keys) {
                $prefix = Str::singular($keys[$index]) . '_';
                $value = collect($value)->mapWithKeys(function ($item, $key) use ($prefix) {
                    if (!Str::startsWith($key, $prefix)) {
                        $key = $prefix . $key;

                    }
                    return [$key => $item];
                });
                return [$keys[$index] => $value];
            });
        });
    }

    private function cartesianProduct($arrays): array
    {
        $result = [[]];
        foreach ($arrays as $index => $propertyValues) {
            $temp = [];
            foreach ($result as $resultItem) {
                foreach ($propertyValues as $propertyValue) {
                    $temp[] = array_merge($resultItem, [$propertyValue]);
                }
            }
            $result = $temp;
        }
        return $result;
    }

    private function sanitizeValue($value)
    {
        if (is_null($value)) {
            return '';
        }

        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }

        return strip_tags($value);
    }


    private function getLoggedTimeInHours($hours, $minutes): float
    {
        return (($hours * 60) + $minutes) / 60;
    }

    private function extractDataForReport($entryQB)
    {
        switch ($this->reportBy) {
            case 'employees':
                return $this->extractDataForEmployees($entryQB);
            case 'departments':
                return $this->extractDataForDepartments($entryQB);
            case 'jobs':
                return $this->extractDataForJobs($entryQB);
            default:
                throw new \Exception("Invalid report type: {$this->reportBy}");
        }
    }

    private function extractDataForEmployees(Builder $entryQB): array
    {
        $entries = $entryQB->select(
            'ts_entries.id as id',
            'ts_jobs.id as entrieable_id',
            'ts_jobs.title as job_name',
            'users.id as user_id',
            'users.name as user_name',
            DB::raw('SUM(ts_entries.hours) as total_hours'),
            DB::raw('SUM(ts_entries.minutes) as total_minutes'),
        )->selectSub(function ($query) {
            $query->from('ts_entries as overall_entries')
                ->selectRaw('SUM((overall_entries.hours * 60) + overall_entries.minutes) / 60')
                ->where(function ($query) {
                    $query->whereDate("overall_entries.spent_at", '>=', $this->startDate)
                        ->whereDate("overall_entries.spent_at", '<=', $this->endDate);
                })
                ->whereColumn('overall_entries.user_id', 'ts_entries.user_id');
        }, 'overall_logged_time')
            ->groupBy('ts_jobs.id', 'users.id')
            ->orderBy('users.id')
            ->get();

        $summary = [
            'all_jobs_logged_time' => 0,
            'employees_total_cost' => 0,
            'number_of_employees' => $entries->unique('user_id')->count(),
        ];
        $entries = $entries->groupBy(['user_id', 'entrieable_id']);

        $reportRecords['records'] = $entries->map(function ($employeeJobEntries) use (&$summary) {
            $employeeRecord = [];

            foreach ($employeeJobEntries as $employeeJobs) {
                foreach ($employeeJobs as $entry) {
                    $user = $entry->user;
                    $periodicSalary = $this->calculateSalaryForPeriod($user, $this->startDate, $this->endDate);
                    $loggedTimeInHours = $this->getLoggedTimeInHours($entry->total_hours, $entry->total_minutes);
                    $hourlyRate = $this->calcAdjustedHourlyRate($periodicSalary, $entry->overall_logged_time);
                    $total_cost = $hourlyRate * $loggedTimeInHours;

                    $summary['employees_total_cost'] += $total_cost;
                    $summary['all_jobs_logged_time'] += $loggedTimeInHours;

                    if (empty($employeeRecord)) {
                        $employeeRecord = [
                            'jobs' => [],
                            'user_id' => $entry->user_id,
                            'user_name' => HtmlElement('a', [
                                    'href' => url(
                                        "ts/entries?user_id={$entry->user->id}&spent_at[from]={$this->startDate->toDateString()}&spent_at[to]={$this->endDate->toDateString()}" . ($this->jobId ? ('&entrieable=' . $this->jobId) : '') . ($this->departmentCode ? ('&department_code=' . $this->departmentCode) : ''))
                                ]
                                , $entry->user->full_name
                            ),
                            'period_all_logged_hours' => \Timesheet::getLoggedTimeInHMFormat($entry->overall_logged_time, 0),
                            'periodic_salary' => round($periodicSalary, 1),
                            'adjusted_rate' => round($hourlyRate, 1),
                            'total_cost' => 0,
                        ];
                    }

                    $employeeRecord['jobs'][] = [
                        'job_id' => $entry->entrieable_id,
                        'job_name' => $entry->job_name,
                        'cost' => \Timesheet::formatMoney(round($total_cost, 1)),
                        'hours' => \Timesheet::getLoggedTimeInHMFormat($entry->total_hours, $entry->total_minutes),
                    ];

                    $employeeRecord['total_cost'] += $total_cost;
                }
            }

            $employeeRecord['total_cost'] = \Timesheet::formatMoney(round($employeeRecord['total_cost'], 1));

            return $employeeRecord;
        });
        $summary['employees_total_cost'] = round($summary['employees_total_cost'], 1);
        $summary['all_jobs_logged_time'] = \Timesheet::getLoggedTimeInHMFormat($summary['all_jobs_logged_time']);
        $reportRecords['summary'] = $summary;

        return $reportRecords;
    }

    private function extractDataForDepartments($entryQB)
    {
        $entries = $entryQB->select(
            'utility_list_of_values.code as department_code',
            'utility_list_of_values.label as department_name',
            DB::raw('SUM(ts_entries.hours) as total_hours'),
            DB::raw('SUM(ts_entries.minutes) as total_minutes'),
            DB::raw('COUNT(DISTINCT ts_entries.user_id) as number_of_employees')
        )->groupBy('utility_list_of_values.code', 'utility_list_of_values.label')->get();

        $summary = [
            'all_departments_logged_time' => 0,
            'entries_employees_count' => 0,
            'employees_total_cost' => 0,
        ];

        $reportRecords['records'] = $entries->map(function ($entry) use (&$summary) {
            $this->reportBy = 'employees';
            $this->departmentCode = $entry->department_code;
            $result = $this->generateReport();

            $totalLoggedTime = $this->getLoggedTimeInHours($entry->total_hours, $entry->total_minutes);

            $summary['all_departments_logged_time'] += $totalLoggedTime;
            $summary['entries_employees_count'] += $entry->number_of_employees;
            $summary['employees_total_cost'] += $result['summary']['employees_total_cost'];

            return [
                'department_code' => $entry->department_code,
                'department_name' => $entry->department_name,
                'total_hours' => \Timesheet::getLoggedTimeInHMFormat($entry->total_hours, $entry->total_minutes),
                'number_of_employees' => $entry->number_of_employees,
                'employees_total_cost' => \Timesheet::formatMoney($result['summary']['employees_total_cost'])
            ];
        });
        $summary['all_departments_logged_time'] = \Timesheet::getLoggedTimeInHMFormat($summary['all_departments_logged_time']);
        $summary['employees_total_cost'] = \Timesheet::formatMoney($summary['employees_total_cost']);
        $reportRecords['summary'] = $summary;

        return $reportRecords;
    }

    private function extractDataForJobs($entryQB)
    {
        $overallJobQB = $this->buildOverallJobQuery();
        $overallDepartmentQB = $this->buildOverallDepartmentQuery();

        $groupedDepartmentEntries = $overallDepartmentQB->groupBy('job_id');

        $totalLoggedJobTimes = Entry::whereIn('entrieable_id', $entryQB->pluck('entrieable_id'))
            ->select(
                'entrieable_id as job_id',
                DB::raw('SUM(hours) as total_hours'),
                DB::raw('SUM(minutes) as total_minutes')
            )
            ->groupBy('entrieable_id')
            ->get()
            ->keyBy('job_id');

        $jobEntries = $entryQB
            ->select(
                'ts_entries.department_code',
                'ts_jobs.id as job_id',
                'ts_jobs.title as job_name',
                DB::raw('COUNT(DISTINCT ts_entries.user_id) as total_employees'),
                DB::raw('SUM(ts_entries.hours) as period_hours'),
                DB::raw('SUM(ts_entries.minutes) as total_minutes')
            )
            ->groupBy('ts_jobs.id', 'ts_jobs.title')
            ->get();

        $totalLoggedDepartmentTimes = Entry::whereIn('entrieable_id', $jobEntries->pluck('job_id'))
            ->select(
                'department_code',
                'ts_entries.entrieable_id as job_id',
                DB::raw('SUM(hours) as total_hours'),
                DB::raw('SUM(minutes) as total_minutes'),
                DB::raw('COUNT(DISTINCT user_id) as total_employees')
            )
            ->groupBy('department_code', 'job_id')
            ->get()
            ->mapWithKeys(function ($item) {
                return [sprintf('%s-%s', $item->department_code, $item->job_id) => $item];
            });

        $periodLoggedDepartmentTimes = Entry::whereIn('entrieable_id', $jobEntries->pluck('job_id'))
            ->select(
                'department_code',
                'ts_entries.entrieable_id as job_id',
                DB::raw('SUM(hours) as total_hours'),
                DB::raw('SUM(minutes) as total_minutes'),
                DB::raw('COUNT(DISTINCT user_id) as total_employees')
            )
            ->where(function ($query) {
                $query->whereDate("ts_entries.spent_at", '>=', $this->startDate)
                    ->whereDate("ts_entries.spent_at", '<=', $this->endDate);
            })
            ->groupBy('department_code', 'job_id')
            ->get()
            ->mapWithKeys(function ($item) {
                return [sprintf('%s-%s', $item->department_code, $item->job_id) => $item];
            });


        $summary = [
            'all_jobs_logged_time' => 0,
            'jobs_count' => $jobEntries->count(),
            'employees_total_cost' => 0,
        ];

        $reportRecords['records'] = $jobEntries->map(function ($job) use ($groupedDepartmentEntries, $overallJobQB, &$summary, $totalLoggedJobTimes, $totalLoggedDepartmentTimes, $periodLoggedDepartmentTimes) {
            $overallJob = $overallJobQB->firstWhere('job_id', $job->job_id);

            $totalLoggedPeriodJobHours = $this->getLoggedTimeInHours($job->period_hours, $job->total_minutes);

            $loggedTimeData = $totalLoggedJobTimes->get($job->job_id);

            $totalLoggedJobHours = $this->getLoggedTimeInHours(
                $loggedTimeData->total_hours ?? 0,
                $loggedTimeData->total_minutes ?? 0
            );

            $completionPercentage = $this->calcPercentage($overallJob->overall_expected_hours, $totalLoggedJobHours);

            if ($this->overUsageJob && $completionPercentage < 100) {
                return null;
            }

            $summary['all_jobs_logged_time'] += $totalLoggedPeriodJobHours;

            $departments = $groupedDepartmentEntries->get($job->job_id, collect())->map(function ($entry) use ($totalLoggedJobHours, $totalLoggedDepartmentTimes, $periodLoggedDepartmentTimes) {
                $totalDepartmentData = $totalLoggedDepartmentTimes->get($entry->department_code . '-' . $entry->job_id);
                $periodDepartmentData = $periodLoggedDepartmentTimes->get($entry->department_code . '-' . $entry->job_id);

                $totalDepartmentJobLoggedTime = $this->getLoggedTimeInHours(
                    $totalDepartmentData->total_hours ?? 0,
                    $totalDepartmentData->total_minutes ?? 0
                );

                $periodDepartmentJobLoggedTime = $this->getLoggedTimeInHours(
                    $periodDepartmentData->total_hours ?? 0,
                    $periodDepartmentData->total_minutes ?? 0
                );

                return [
                    'department_name' => $entry->department_name,
                    'department_employees' => $periodDepartmentData->total_employees ?? 0,
                    'period_hours' => \Timesheet::getLoggedTimeInHMFormat($periodDepartmentJobLoggedTime, 0),
                    'department_expected_hours' => $entry->department_expected_hours,
                    'completion_percentage' => $this->calcPercentage($entry->department_expected_hours, $totalDepartmentJobLoggedTime),
                    'job_percentage' => $this->calcPercentage($totalLoggedJobHours, $totalDepartmentJobLoggedTime),
                ];
            })->toArray();

            $this->reportBy = 'employees';
            $this->jobId = $job->job_id;
            $result = $this->generateReport();

            $summary['employees_total_cost'] += $result['summary']['employees_total_cost'];

            return [
                'job_id' => $job->job_id,
                'job_name' => $job->job_name,
                'number_of_employees' => $job->total_employees,
                'period_hours' => \Timesheet::getLoggedTimeInHMFormat($totalLoggedPeriodJobHours, 0),
                'overall_expected_hours' => $overallJob->overall_expected_hours,
                'completion_percentage' => $completionPercentage,
                'departments' => $departments,
                'employees_total_cost' => \Timesheet::formatMoney($result['summary']['employees_total_cost']),
            ];
        })->filter()->values()->all();

        $summary['all_jobs_logged_time'] = \Timesheet::getLoggedTimeInHMFormat($summary['all_jobs_logged_time']);
        $summary['employees_total_cost'] = \Timesheet::formatMoney($summary['employees_total_cost']);

        $reportRecords['summary'] = $summary;

        return $reportRecords;
    }

    private function buildOverallJobQuery()
    {
        $subQuery = DB::table('ts_department_job')
            ->select('job_id', DB::raw('SUM(hours) as total_hours'))
            ->groupBy('job_id');

        return Entry::query()
            ->join('ts_jobs', 'ts_entries.entrieable_id', '=', 'ts_jobs.id')
            ->joinSub($subQuery, 'job_hours', function ($join) {
                $join->on('ts_jobs.id', '=', 'job_hours.job_id');
            })
            ->select(
                'ts_jobs.id as job_id',
                'ts_jobs.title as job_name',
                DB::raw('COUNT(DISTINCT ts_entries.user_id) as total_employees'),
                DB::raw('SUM(ts_entries.hours) as period_hours'),
                DB::raw('SUM(ts_entries.minutes) as total_minutes'),
                'job_hours.total_hours as overall_expected_hours'
            )
            ->groupBy('ts_jobs.id', 'ts_jobs.title', 'job_hours.total_hours')
            ->get();
    }

    private function buildOverallDepartmentQuery()
    {
        return Entry::query()
            ->join('ts_jobs', 'ts_entries.entrieable_id', '=', 'ts_jobs.id')
            ->join('ts_department_job', 'ts_jobs.id', '=', 'ts_department_job.job_id')
            ->join('utility_list_of_values', 'ts_department_job.department_code', '=', 'utility_list_of_values.code')
            ->select(
                'ts_jobs.id as job_id',
                'ts_jobs.title as title',
                'utility_list_of_values.code as department_code',
                'utility_list_of_values.label as department_name',
                'ts_department_job.hours as department_expected_hours',
                DB::raw('COUNT(DISTINCT ts_entries.user_id) as department_employees'),
                DB::raw('SUM(ts_entries.hours) as department_hours'),
                DB::raw('SUM(ts_entries.minutes) as department_minutes')
            )
            ->groupBy('ts_jobs.id', 'ts_jobs.title', 'utility_list_of_values.code')
            ->get();
    }

    private function calcPercentage(float $totalJobHours, float $departmentJobLoggedTime)
    {
        return round($departmentJobLoggedTime / $totalJobHours * 100, 2);
    }

    private function calcAdjustedHourlyRate($salary, $loggedTime)
    {
        return $loggedTime == 0 ? 0 : $salary / $loggedTime;
    }

    public function calculateSalaryForPeriod(User $user, Carbon $startDate, Carbon $endDate): float
    {
        $startPeriod = $startDate->copy();
        $endPeriod = $endDate->copy();

        $salaryBreakDown = [];

        while ($startPeriod->lte($endPeriod)) {
            if (!isset($salaryBreakDown[$startPeriod->shortMonthName])) {
                $working_days = \Corals\Modules\Timesheet\Facades\Timesheet::calculateWorkingDays($startPeriod, null, true);
                $working_hours = $working_days * config('timesheet.hours_per_day');
                $hourlyRate = $user->salary / $working_hours;

                if ($startPeriod->isSameMonth($endPeriod)) {
                    $endPeriodMonth = $endPeriod->copy();
                } else {
                    $endPeriodMonth = $startPeriod->copy()->endOfMonth();
                }

                $salaryBreakDown[$startPeriod->shortMonthName] = [
                    'working_days' => $working_days,
                    'working_hours' => $working_hours,
                    'hourly_rate' => $hourlyRate,
                    'salary' => \Corals\Modules\Timesheet\Facades\Timesheet::calculateWorkingDays($startPeriod, $endPeriodMonth) * config('timesheet.hours_per_day') * $hourlyRate
                ];
            }

            $startPeriod->addMonth()->startOfMonth();
        }

        return collect($salaryBreakDown)->sum('salary');
    }
}

Spamworldpro Mini