File: D:/HostingSpaces/SBogers10/hours.komma.pro/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);
}
}
}
}