![]() 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/ts.corals.io/corals-api/Corals/modules/Timesheet/Services/ |
<?php namespace Corals\Modules\Timesheet\Services; use Carbon\Carbon; use Corals\Foundation\Services\BaseServiceClass; use Corals\Modules\Timesheet\Classes\BagParameters; use Corals\Modules\Timesheet\Classes\InvoiceGenerator; use Corals\Modules\Timesheet\Facades\Timesheet; use Corals\Modules\Timesheet\Models\Activity; use Corals\Modules\Timesheet\Models\Client; use Corals\Modules\Timesheet\Models\Entry; use Corals\Modules\Timesheet\Models\Project; use Corals\Settings\Facades\Settings; use Corals\User\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; use Illuminate\Support\Arr; class EntryService extends BaseServiceClass { protected $excludedRequestParams = [ 'tags' ]; /** * @var null */ protected $client = null; /** * @return array */ public function getFormData(Request $request) { if (!user()->canAny(['create', 'update'], new Entry())) { return []; } if (user()->hasRole('member')) { $latestEntry = Entry::query() ->join('timesheet_activities', 'timesheet_activities.id', 'timesheet_entries.activity_id') ->where('user_id', user()->id) ->where('timesheet_activities.is_limited', 0) ->where('timesheet_activities.is_special', 0) ->select('timesheet_entries.*') ->latest() ->first(); if ($latestEntry) { $default = [ 'activity_id' => $latestEntry->activity_id, 'user_id' => $latestEntry->user_id, 'project_id' => $latestEntry->project_id ]; if (!$request->has('project_id')) { $request->merge(['project_id' => $latestEntry->project_id]); } } else { $default = [ 'user_id' => user()->user_id, ]; } } //get default date from latest entry for user if not superuser. return [ 'activities' => $this->getActivitiesFormData($request), 'users' => $this->getUsersFormData($request), 'projects' => Timesheet::getProjectsFormData(['logged_in_user' => true]), 'defaults' => $default ?? [] ]; } /** * @return array[] */ protected function getActivitiesFormData(Request $request) { $billable = []; $nonBillable = []; Activity::query() ->select('timesheet_activities.*') ->join('timesheet_project_activity', 'timesheet_activities.id', '=', 'timesheet_project_activity.activity_id') ->where('timesheet_project_activity.project_id', '=', $request->get('project_id')) ->when(!Timesheet::isTimesheetAdministration(), function ($query) { $query->where('is_limited', 0) ->where('is_special', 0); }) ->each(function (Activity $activity) use (&$billable, &$nonBillable) { if ($activity->billable) { $billable[] = [ 'label' => $activity->title, 'value' => $activity->id, 'group' => 'billable', 'group_label' => 'Billable', 'is_active' => $activity->is_active ]; } else { $nonBillable[] = [ 'label' => $activity->title, 'value' => $activity->id, 'group' => 'non_billable', 'group_label' => 'Non Billable', 'is_active' => $activity->is_active ]; } }); return array_merge([ [ 'code' => 'billable', 'group' => true, 'status' => 'active', 'label' => 'Billable', 'is_group_empty' => !count($billable), 'empty_group_message' => "No Options!", 'notSelectable' => true ], ...$billable, ], $nonBillable ? [ [ 'code' => 'non_billable', 'group' => true, 'status' => 'active', 'label' => 'Non-Billable', 'is_group_empty' => !count($nonBillable), 'empty_group_message' => "No Options!", 'notSelectable' => true ], ...$nonBillable ] : []); } protected function getUsersFormData(Request $request) { $project = Project::find($request->get('project_id')); $users = User::query() ->select('users.*') ->selectRaw("concat(users.name,' ',users.last_name,', ',users.email, (CASE WHEN users.status = 'inactive' THEN ' (Inactive)' ELSE '' END)) as label, users.id as value"); if ($project && $project->assignable) { $users->join('timesheet_project_user', 'users.id', '=', 'timesheet_project_user.user_id') ->where('timesheet_project_user.project_id', '=', $request->get('project_id')); } $users->whereHas('roles', function (Builder $query) { $query->where('roles.name', '=', 'member'); }); $users->when(!Timesheet::isTimesheetAdministration(), function ($query) { $query->where('users.id', user()->id); }); return $users->get(); } public function setClient($client) { $this->client = $client; return $this; } protected function isClientExists() { if (!$this->client) { throw new \Exception('Client not set'); } } /** * @return array * @throws \Exception */ public function entriesPerBillingCycle() { $this->isClientExists(); $nonBilledClientLevelEntries = $this->getEntriesPerBillingCycleBaseQuery() ->whereNull('timesheet_projects.bill_cycle') ->get()->groupBy('project_id', true); $result = []; foreach ($nonBilledClientLevelEntries as $projectId => $entries) { $result[] = $this->buildProjectEntries($projectId, $entries); } $nonBilledProjectLevelEntries = $this->getEntriesPerBillingCycleBaseQuery() ->whereNotNull('timesheet_projects.bill_cycle') ->get()->groupBy('project_id', true); foreach ($nonBilledProjectLevelEntries as $projectId => $entries) { $result[] = $this->buildProjectEntries($projectId, $entries, true); } return $result; } protected function buildProjectEntries($projectId, $entries, $perProject = false) { $project = Project::find($projectId); $projectEntries = Arr::only($project->toArray(), ['id', 'name', 'budget', 'hourly_rate', 'currency']); $cycles = []; foreach ($entries as $entry) { if (is_null($entry->evaluation_hours) && is_null($entry->evaluation_minutes)) { continue; } $cycle = $this->getBillingCycleEdges($entry->spent_at, $perProject ? $project : $this->client); if (!$cycle) { continue; } if (!isset($cycles[$cycle['code']])) { $cycle['entries'] = []; $cycle['total_evaluation_hours'] = 0; $cycle['total_evaluation_minutes'] = 0; $cycle['has_unreviewed_entries'] = false; $cycles[$cycle['code']] = $cycle; } /** * @var Entry $entry */ $spentAt = Carbon::parse($entry->spent_at); $cycles[$cycle['code']]['entries'][] = [ 'id' => $entry->id, 'spent_at' => $entry->spent_at, 'spent_at_day' => [ 'name' => $spentAt->englishDayOfWeek, 'short_name' => $spentAt->shortEnglishDayOfWeek ], 'has_reviewed' => $entry->has_reviewed, 'user' => $entry->user->full_name, 'description' => $entry->description, 'activity' => $entry->activity->title, 'hourly_rate' => $entry->activity->hourly_rate, 'evaluation_hours' => $entry['evaluation_hours'], 'evaluation_minutes' => $entry['evaluation_minutes'], 'evaluation_time' => Timesheet::formatHoursAndMinutes($entry['evaluation_hours'], $entry['evaluation_minutes']), 'labels' => $entry->present('labels') ]; if (!$entry->has_reviewed) { $cycles[$cycle['code']]['has_unreviewed_entries'] = true; } $cycles[$cycle['code']]['total_evaluation_hours'] += $entry['evaluation_hours']; $cycles[$cycle['code']]['total_evaluation_minutes'] += $entry['evaluation_minutes']; $cycles[$cycle['code']]['total_evaluation_time'] = Timesheet::formatHoursAndMinutes( $cycles[$cycle['code']]['total_evaluation_hours'], $cycles[$cycle['code']]['total_evaluation_minutes']); } foreach ($cycles as $code => $cycle) { $cycle['entries'] = array_values(collect($cycle['entries']) ->sortByDesc('spent_at') ->groupBy('spent_at') ->toArray()); foreach ($cycle['entries'] as $index => $entry) { $cycle['entries'][$index] = array_values(collect($entry)->sortBy('user')->toArray()); } $cycles[$code] = $cycle; } $projectEntries['cycles'] = $cycles; return $projectEntries; } protected function getEntriesPerBillingCycleBaseQuery(): Builder { $this->isClientExists(); return Entry::query() ->join('timesheet_projects', 'timesheet_projects.id', 'timesheet_entries.project_id') ->where('timesheet_projects.type', '=', 'time_and_materials') ->join('timesheet_activities', 'timesheet_activities.id', 'timesheet_entries.activity_id') ->join('timesheet_clients', 'timesheet_clients.id', 'timesheet_projects.client_id') ->whereNull('timesheet_entries.invoice_id') ->whereNull('timesheet_projects.budget') ->when(!Timesheet::isTimesheetAdministration(), function ($query) { $query->where('timesheet_entries.has_reviewed', 1); }) ->where('timesheet_clients.id', $this->client->id) ->where('timesheet_activities.billable', 1) ->orderBy('timesheet_entries.spent_at') ->select('timesheet_entries.*'); } public function getBillingCycleEdges($spentAt, $object) { $this->isClientExists(); $billingCycle = $object->bill_cycle ?? 'monthly'; $billingCycleStartsAt = $object->bill_cycle_starts_at ?? 1; $spentAt = Carbon::parse($spentAt); $date = Carbon::parse($spentAt); switch ($billingCycle) { case 'daily': return [ 'code' => sprintf('%s-%s', $date->format('Ymd'), $date->format('Ymd')), 'start' => $date->toDateString(), 'end' => $date->toDateString(), 'is_current' => now()->isSameDay($date) ]; case 'weekly': $billingCycleEdge = $date->startOfWeek(Settings::get('timesheet_start_of_week', Carbon::MONDAY)); if ($spentAt->lt($billingCycleEdge)) { $startOfWeek = $billingCycleEdge->subWeek(); } else { $startOfWeek = $billingCycleEdge; } $endOfWeek = $startOfWeek->copy()->addWeek(); return [ 'code' => sprintf('%s-%s', $startOfWeek->format('Ymd'), $endOfWeek->format('Ymd')), 'start' => $startOfWeek->toDateString(), 'end' => $endOfWeek->toDateString(), 'is_current' => now()->between($startOfWeek, $endOfWeek) ]; case 'biweekly': $billingCycleEdge = $date->copy()->startOfMonth()->addDays($billingCycleStartsAt - 1); if ($spentAt->lt($billingCycleEdge)) { $startOfBiweekly = $billingCycleEdge->subWeeks(2); } elseif ($spentAt->gte($billingCycleEdge->copy()->addWeeks(2))) { $startOfBiweekly = $billingCycleEdge->addWeeks(2); } else { $startOfBiweekly = $billingCycleEdge; } $endOfBiweekly = $startOfBiweekly->copy()->addWeeks(2); return [ 'code' => sprintf('%s-%s', $startOfBiweekly->format('Ymd'), $endOfBiweekly->format('Ymd')), 'start' => $startOfBiweekly->toDateString(), 'end' => $endOfBiweekly->toDateString(), 'is_current' => now()->between($startOfBiweekly, $endOfBiweekly) ]; case 'monthly': $billingCycleEdge = $date->copy()->startOfMonth()->addDays($billingCycleStartsAt - 1); if ($spentAt->lt($billingCycleEdge)) { $startOfMonth = $billingCycleEdge->subMonth(); } else { $startOfMonth = $billingCycleEdge; } $endOfMonth = $startOfMonth->copy()->addMonth(); return [ 'code' => sprintf('%s-%s', $startOfMonth->format('Ymd'), $endOfMonth->format('Ymd')), 'start' => $startOfMonth->toDateString(), 'end' => $endOfMonth->toDateString(), 'is_current' => now()->between($startOfMonth, $endOfMonth) ]; } } /** * @param Request $request * @param Project $project * @param Client $client * @return array */ public function generateInvoicePerCycle(Request $request, Project $project, Client $client) { $bagParameters = new BagParameters([ 'project' => $project, 'client' => $client, 'status' => $request->get('status', 'pending'), 'include_previous_entries' => $request->get('include_previous_entries', 0), 'date_range' => [ $request->get('cycle_start'), $request->get('cycle_end') ] ]); return (new InvoiceGenerator($bagParameters))->generate(); } }