HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/MdnDirecteur/hours.komma.cloud/app/Komma/Hours/HourService.php
<?php

namespace App\Komma\Hours;


use Carbon\Carbon;
use App\Komma\Absences\Absence;
use App\Komma\Messages\MessageController;
use App\Komma\Notifications\NotificationService;
use App\Komma\Projects\Project;
use App\Komma\Subprojects\Subproject;
use App\Komma\Subprojects\SubprojectService;
use App\Komma\Tasks\TaskService;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;

class HourService
{
    private $subprojectService;
    private $messageController;
    private $taskService;

    public function __construct(
        MessageController $messageController,
        SubprojectService $subprojectService,
        TaskService $taskService,
        NotificationService $notificationService
    ) {
        $this->subprojectService = $subprojectService;
        $this->messageController = $messageController;
        $this->taskService = $taskService;
        $this->notificationService = $notificationService;
    }


    /**
     * Returns all the hours in a month, based on any given date
     * @param $date
     * @return \Illuminate\Database\Eloquent\Collection|static[]
     */
    public function getMonth($date, $user)
    {
        return Hour::with([
            'Task.Subproject.Project',
        ])->where('user_id', $user)
            ->where('date', '>=', $this->getCalendar($date)['calendarStartMonth'])
            ->where('date', '<', $this->getCalendar($date)['calendarEndMonth'])
            ->get();
    }

    /**
     * Returns all the absences in a month, based on any given date
     *
     * @param $date
     * @return \Illuminate\Database\Eloquent\Collection|static[]
     */
    public function getAbsenceMonth($date, $user)
    {
        return Absence::with([
        ])->where('user_id', $user)
            ->where('date', '>=', $this->getCalendar($date)['calendarStartMonth'])
            ->where('date', '<', $this->getCalendar($date)['calendarEndMonth'])
            ->get();
    }

    /**
     * Returns the start and end of a month, based on any given date
     *
     * @param $date
     * @return array
     */
    protected function getCalendar($date)
    {
        //create dates
        $calendarStartMonth = Carbon::parse($date)->startOfMonth()->startOfWeek();
        $calendarEndMonth = Carbon::parse($date)->endOfMonth()->endOfWeek();

        //return
        return compact('calendarStartMonth', 'calendarEndMonth');
    }


    /**
     * @param $request
     * @param $hour
     * @param $hours
     * @param $user
     * @param $restHours
     * @param $exceed
     * @param $created_at
     * @return
     * @internal param $subprojectTotalHours
     */
    public function storeHour($request, $hour, $user, $exceed, $created_at)
    {
        //store/update hour
        $hour->user_id = $user;
        $hour->task_id = $request->task;
        $hour->value = $request->hours;
        $hour->margin = $request->margin;
        $hour->billable = $exceed == 1 ? 0 : $request->billable;
        $hour->billed_at = $request->billed ? Carbon::now() : null;
        $hour->exceed_subproject = $exceed;
        $hour->bug = $request->bug;
        $hour->description = $request->description;
        $hour->intern_description = $request->internDescription;
        $hour->date = Carbon::parse($request->date)->startOfDay();

        // PowerBI identifiers
        $hour->project_id = $request->project;
        $hour->subproject_id = $request->subproject;
        $subproject = Subproject::find($request->subproject);
        $hour->subproject_hourly_rate = $subproject->hourly_rate;
        $hour->company_id = $subproject->Project->Company->id;

        if (!empty($created_at)) {
            $hour->created_at = $created_at;
        }
        $hour->save();

        //return
        return $hour;
    }


    /**
     * Used to update the hours in a Subproject when that Subproject is updated
     * and might have a different budget than before
     * Some hours then might exceed the budget
     *
     * @param $collection
     * @param $budget
     */
    public function updateOutBudget($collection, $subproject)
    {
        $count = [];
        //loop trough sorted hours
        foreach ($collection as $key => $hour) {
            if ($subproject->budget > 0) {
                if ($hour->billable == 1 and array_sum($count) < $subproject->budget and array_sum($count) + $hour->value > $subproject->budget) {
                    $range = $subproject->budget - array_sum($count);
                    $this->split($hour, $range, 0, 1);
                }
                if ($hour->billable == 1) {
                    $count += [$key => $hour->value];
                }
                if (array_sum($count) > $subproject->budget) {
                    $hour->billable = 0;
                    $hour->exceed_subproject = 1;
                    $hour->save();
                } else {
                    $hour->billable = $subproject->billable;
                    $hour->exceed_subproject = 0;
                    $hour->save();
                }
            } else {
                $hour->billable = $subproject->billable;
                $hour->exceed_subproject = 0;
                $hour->save();
            }
        }
    }


    /**
     * @param $subproject
     * @return Collection
     */
    public function getHours($subproject)
    {
        //create collection
        $hours = collect();

        //loop trough tasks
        foreach ($subproject->Tasks as $task) {
            $task->Subproject()->associate($subproject);
            //loop trough hours
            foreach ($task->Hours as $hour) {
                $hour->Task()->associate($task);
                //push hour in collection
                $hours->push($hour);
            }
        }
        //sort hours
        return $hours->sortByDesc("date");
    }

    /**
     * @param $request
     * @param $hour
     * @return $user
     */
    public function updateFromRequest($request, $hour)
    {
        //find hour
        $hour = Hour::find($hour);
        $this->processHourFromRequest($request, $hour, true);
    }

    /**
     * @param $request
     * @return $user
     */
    public function saveFromRequest($request)
    {
        $hour = new Hour();
        $this->processHourFromRequest($request, $hour, false);
    }

    /**
     * TODO: comments toevoegen
     * @param $hour
     * @param $request
     * @param $user
     * @param NotificationService $notificationService
     */
    public function splitOrStoreHour($hour, $request, $user, $update)
    {
        //find subproject and get all hours
        $subproject = Subproject::find($request->subproject);
        $subprojectHours = $this->subprojectService->getSumSubprojectHours($subproject);
        $subprojectTotalHours = array_sum($subprojectHours['totalBillWithBudget']) + array_sum($subprojectHours['totalBillWithoutBudget']) + array_sum($subprojectHours['totalNotBillWithBudget']) + array_sum($subprojectHours['totalNotBillWithoutBudget']); // + array_sum($subprojectHours['totalExceedsSubProject']);
        $availableHours = $subproject->budget - $subprojectTotalHours;

        if ($subproject->budget == 0) {
            // the subproject has no budget, so just store the hour
            $this->storeHour($request, $hour, $user, 0, 0);
        } else {
            // there is no budget left, mark the new hour as exceeds
            if (!$update && $availableHours <= 0) {
                $this->storeHour($request, $hour, $user, 1, 0);
            } else {

                // when updating an hour, we need to check if the difference will exceed the budget
                if ($update && $hour->exceed_subproject == 0) {
                    $subprojectTotalHours -= $hour->value;
                    $availableHours = $subproject->budget - $subprojectTotalHours;
                }

                // check if the hours exceed the available hours
                if ($availableHours > 0 && $request->hours > $availableHours) {
                    $this->storeHour($request, $hour, $user, 0, 0);
                    $this->split($hour, abs($availableHours), $hour->billable, 1);

                    // hours fit in the budget
                } elseif ($availableHours > 0 && $request->hours <= $availableHours) {
                    $this->storeHour($request, $hour, $user, 0, 0);
                    // hours completely exceed the budget (or the budget was already exceeded
                } else {
                    $this->storeHour($request, $hour, $user, 1, 0);
                }

            }
        }

        $subprojectTotalHours += $request->hours;
        $this->sendNotificationToAdmin($subproject, $subprojectTotalHours, $availableHours);

        // check if we need to correct exceedances now that the budget might have changed
        $this->recalculateSubProjectExceedances($subproject);

    }

    // If there are hours left in the budget and there are hours that are marked as exceeding the budget, this function will correct them (newer hours first)
    // !!! only corrects hours that exceeds a subprojects budget, it does not set hours to exceed
    /**
     * TODO: comments toevoegen
     * @param $subproject
     */
    public function recalculateSubProjectExceedances($subproject)
    {
        $subprojectHours = $this->subprojectService->getSumSubprojectHours($subproject);
        $subprojectTotalHours = array_sum($subprojectHours['totalBillWithBudget']) + array_sum($subprojectHours['totalBillWithoutBudget']) + array_sum($subprojectHours['totalNotBillWithBudget']) + array_sum($subprojectHours['totalNotBillWithoutBudget']) + array_sum($subprojectHours['totalExceedsSubProject']);
        $availableHours = $subproject->budget - ($subprojectTotalHours - array_sum($subprojectHours['totalExceedsSubProject']));

        if ($subproject->budget > 0 && array_sum($subprojectHours['totalExceedsSubProject']) > 0 && $subproject->budget >= ($subprojectTotalHours - array_sum($subprojectHours['totalExceedsSubProject']))) {
            foreach ($subproject->Tasks as $task) {
                foreach ($task->Hours as $hour) {
                    if ($hour->exceed_subproject == 1) {


                        if ($availableHours > 0) {
                            if ($hour->value > $availableHours) {
                                $newHour = $hour->replicate();
                                $newHour->value = $availableHours;
                                $newHour->exceed_subproject = 0;
                                $newHour->save();

                                $hour->value = $hour->value - $availableHours;
                                $hour->save();
                                $availableHours = 0;
                            } else {
                                $hour->exceed_subproject = 0;
                                $hour->save();
                                $availableHours -= $hour->value;
                            }
                        }
                    }
                }
            }
        }

    }

    // Split an hour into multiple hours when it exceeds the given range (for instance the remaining hours in the budget of a subproject)
    /**
     * TODO: comments toevoegen
     * @param $hour
     * @param $range
     * @param $billable
     * @param int $exceed
     */
    public function split($hour, $range, $billable, $exceed = 0)
    {
        //copied alternative hour
        $newHour = $hour->replicate();
        $newHour->value = $hour->value - $range;
        $newHour->billable = $exceed == 1 ? 0 : 1;
        $newHour->exceed_subproject = $exceed;
        $newHour->save();

        //original hour
        $hour->value = $range;
        $hour->save();

        //message + activity
        $subject = Hour::find($hour->id);
        $value = "Uren";
        $this->messageController->changed($value, $subject);
    }

    /**
     * @param $hour
     */
    public function destroy($hour, $delete = null)
    {
        //delete hour
        $getHour = Hour::find($hour);
        $getSubproject = $getHour->Task->Subproject;
        $getHour->delete();

        $this->recalculateSubProjectExceedances($getSubproject);

        //message + activity
        $value = "Uur";
        $this->messageController->destroyed($value, $getHour, $delete);
    }


    /**
     * @param $hour
     */
    public function restore($hour)
    {
        \DB::transaction(function () use ($hour) {
            Hour::withTrashed()->find($hour)->restore();

            //message + activity
            $subject = Hour::find($hour);
            $value = "Uur";
            $this->messageController->recovered($value, $subject);
        });
    }


    /**
     * @param $getHour
     * @param $hour
     * @param $request
     */
    public function updateSelectedHour($getHour, $hour, $request)
    {

        $taskId = $this->firstOrCreate($getHour, $request->subproject)->id;
        //make request complete
        $request->request->add(['task' => $taskId]);
        $request->request->add(['hours' => $getHour->value]);
        $request->filled('billable') ? $request->get('billable') : $request->request->add(['billable' => $getHour->billable]);
        $request->request->add(['bug' => $getHour->bug]);
        $request->request->add(['description' => $getHour->description]);
        $request->request->add(['internDescription' => $getHour->intern_description]);
        $request->request->add(['date' => $getHour->date]);
        //update hour
        $this->updateFromRequest($request, $hour);
    }


    /**
     * @param $hour
     * @param $newSubproject
     * @return \Hours\Tasks\Task
     */
    private function firstOrCreate($hour, $newSubproject)
    {
        $taskTempId = $hour->Task->task_template_id;
        $subproject = Subproject::with(['Tasks'])->find($newSubproject);

        if ($task = $subproject->Tasks->where('task_template_id', $taskTempId)->first()) {
            return $task;
        } else {
            $request = ['subproject' => $subproject->id];
            $request += ['task' => $taskTempId];

            return $task = $this->taskService->store((object)$request);
        }
    }

    /**
     * @param $request
     * @param $hour
     * @return $user
     */
    private function processHourFromRequest($request, $hour, $update)
    {
        //transform request to get correct data for database
        $request->bug = $request->bug ? 1 : 0;
        $user = $request->filled('user') ? $request->get('user') : (!empty($hour->user_id) ? $hour->user_id : Auth::id());

        $this->splitOrStoreHour($hour, $request, $user, $update);

        //message + activity
        $subject = Hour::find($hour->id);
        $value = "Uur";
        if ($subject) {
            $this->messageController->saved($value, $subject);
        }
    }

    /**
     * @param $id
     * @return mixed
     */
    public function getHourByID($id)
    {
        return Hour::where('id', $id)->first();
    }

    /**
     * @param $hours
     * @param $billable
     */
    public function updateBillable($hours, $billable)
    {
        //loop trough sorted hours
        foreach ($hours as $key => $hour) {
            if ($hour->exceed_subproject == 0) {
                $hour->billable = $billable;
                $hour->save();
            }
        }
    }

    /**
     * TODO: comments toevoegen
     * @param $subproject
     * @param $subprojectTotalHours
     * @param $availableHours
     */
    private function sendNotificationToAdmin($subproject, $subprojectTotalHours, $availableHours)
    {
        if ($subproject->budget > 0) {
            \Log::debug('HourService::sendNotificationToAdmin => (' . $subprojectTotalHours .' / '. $subproject->budget .') = '.($subprojectTotalHours / $subproject->budget).' >= 0.9');
            if (($subprojectTotalHours / $subproject->budget) >= 1) {
                $tresh = "Volledig";
                $this->notificationService->budgetNotification($subproject, $tresh);
            } elseif (($subprojectTotalHours / $subproject->budget) >= 0.9) {
                $tresh = "90% van het budget";
                $this->notificationService->budgetNotification($subproject, $tresh);
            } elseif (($subprojectTotalHours / $subproject->budget) >= 0.75) {
                $tresh = "75% van het budget";
                $this->notificationService->budgetNotification($subproject, $tresh);
            }
        }
    }

}