268 lines
8.5 KiB
PHP
268 lines
8.5 KiB
PHP
<?php
|
|
|
|
namespace Modules\ManagementTools\Utils;
|
|
|
|
use Carbon\Carbon;
|
|
use Carbon\CarbonPeriod;
|
|
use Illuminate\Support\Collection;
|
|
use Modules\ManagementTools\Entities\MtDailyReport;
|
|
use Modules\ManagementTools\Entities\MtUserReportSetting;
|
|
|
|
class DailyReportUtil
|
|
{
|
|
public function workdayStartHour(): int
|
|
{
|
|
return (int) config('managementtools.workday_start_hour', 7);
|
|
}
|
|
|
|
/**
|
|
* Workday runs from 07:00 of $day until 07:00 next day.
|
|
*/
|
|
public function getWorkdayBounds(?string $day = null): array
|
|
{
|
|
$base = $day ? Carbon::parse($day) : Carbon::today();
|
|
|
|
$start = $base->copy()->startOfDay()->addHours($this->workdayStartHour());
|
|
$end = $base->copy()->addDay()->startOfDay()->addHours($this->workdayStartHour());
|
|
|
|
return [$start, $end];
|
|
}
|
|
|
|
/**
|
|
* Time slots for the workday (interval in minutes).
|
|
*/
|
|
public function generateTimeSlots(Carbon $start, Carbon $end, int $intervalMinutes = 15): array
|
|
{
|
|
if ($intervalMinutes >= 1440) {
|
|
return [$start->format('Y-m-d H:i:s')];
|
|
}
|
|
|
|
$period = CarbonPeriod::create($start, $intervalMinutes.' minutes', $end);
|
|
$slots = [];
|
|
foreach ($period as $date) {
|
|
$slots[] = $date->format('Y-m-d H:i:s');
|
|
}
|
|
array_pop($slots);
|
|
|
|
return $slots;
|
|
}
|
|
|
|
public function generateTimeSlotsForUser(int $business_id, int $user_id, Carbon $start, Carbon $end, ?string $day = null): array
|
|
{
|
|
$settings = MtUserReportSetting::resolveForUser($business_id, $user_id);
|
|
|
|
if ($settings->isMonthlyMode()) {
|
|
if ($day && ! $settings->allowsReportingOnDay($day)) {
|
|
return [];
|
|
}
|
|
|
|
return [$start->format('Y-m-d H:i:s')];
|
|
}
|
|
|
|
if ($settings->isSingleSlotDay()) {
|
|
return [$start->format('Y-m-d H:i:s')];
|
|
}
|
|
|
|
return $this->generateTimeSlots($start, $end, $settings->slotIntervalMinutes());
|
|
}
|
|
|
|
public function generateRecentDays(int $count = 15): array
|
|
{
|
|
$start = Carbon::today()->subDays($count);
|
|
$end = Carbon::today();
|
|
$period = CarbonPeriod::create($start, '1 day', $end);
|
|
$days = [];
|
|
foreach ($period as $date) {
|
|
$days[] = $date->format('Y-m-d');
|
|
}
|
|
|
|
return $days;
|
|
}
|
|
|
|
/**
|
|
* Map slot => report for a user within workday bounds.
|
|
*/
|
|
public function getUserSlotReports(int $business_id, int $user_id, Carbon $start, Carbon $end): Collection
|
|
{
|
|
return MtDailyReport::forBusiness($business_id)
|
|
->where('user_id', $user_id)
|
|
->whereBetween('reported_at', [$start, $end])
|
|
->with(['comments', 'userTask'])
|
|
->orderBy('reported_at')
|
|
->get()
|
|
->keyBy(function ($report) {
|
|
return $report->reported_at->format('Y-m-d H:i:s');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Grouped duplicate bodies for admin views (same logic as Grow CRM).
|
|
*/
|
|
public function getGroupedReportsForRange(int $business_id, Carbon $start, Carbon $end, ?int $user_id = null): Collection
|
|
{
|
|
$query = MtDailyReport::forBusiness($business_id)
|
|
->selectRaw('body, COUNT(body) as body_count, MIN(reported_at) as first_slot, MAX(reported_at) as last_slot, MIN(id) as id, user_id, score, task_id, mt_user_task_id, user_status, MIN(created_at) as first_created, MAX(updated_at) as last_updated')
|
|
->whereBetween('reported_at', [$start, $end])
|
|
->groupBy('body', 'user_id', 'score', 'task_id', 'mt_user_task_id', 'user_status')
|
|
->havingRaw('COUNT(*) >= 1')
|
|
->with(['user', 'comments.user'])
|
|
->orderBy('first_slot');
|
|
|
|
if ($user_id) {
|
|
$query->where('user_id', $user_id);
|
|
}
|
|
|
|
return $query->get()->groupBy('user_id');
|
|
}
|
|
|
|
public function userStatusLabel(?int $status): string
|
|
{
|
|
$map = [
|
|
0 => __('managementtools::lang.status_select'),
|
|
1 => __('managementtools::lang.status_absent'),
|
|
2 => __('managementtools::lang.status_leave'),
|
|
3 => __('managementtools::lang.status_present'),
|
|
4 => __('managementtools::lang.status_mission'),
|
|
];
|
|
|
|
return $map[$status ?? 0] ?? '—';
|
|
}
|
|
|
|
public function scoreLabel(?int $score): string
|
|
{
|
|
$map = [
|
|
1 => __('managementtools::lang.score_very_weak'),
|
|
2 => __('managementtools::lang.score_weak'),
|
|
3 => __('managementtools::lang.score_average'),
|
|
4 => __('managementtools::lang.score_good'),
|
|
5 => __('managementtools::lang.score_excellent'),
|
|
];
|
|
|
|
return $map[$score ?? 0] ?? '—';
|
|
}
|
|
|
|
public function todayDate(): string
|
|
{
|
|
return Carbon::today()->format('Y-m-d');
|
|
}
|
|
|
|
public function isEditableDay(?string $day): bool
|
|
{
|
|
if (empty($day)) {
|
|
return true;
|
|
}
|
|
|
|
return Carbon::parse($day)->isToday();
|
|
}
|
|
|
|
public function isPastDay(?string $day): bool
|
|
{
|
|
if (empty($day)) {
|
|
return false;
|
|
}
|
|
|
|
return Carbon::parse($day)->lt(Carbon::today());
|
|
}
|
|
|
|
/**
|
|
* Task-based reports for a calendar day (excludes day-summary row where task id is null).
|
|
*/
|
|
public function getTaskReportsForDay(int $business_id, int $user_id, string $day): Collection
|
|
{
|
|
return MtDailyReport::forBusiness($business_id)
|
|
->where('user_id', $user_id)
|
|
->whereDate('report_date', $day)
|
|
->whereNotNull('mt_user_task_id')
|
|
->with('userTask')
|
|
->get()
|
|
->keyBy('mt_user_task_id');
|
|
}
|
|
|
|
/**
|
|
* Day-level attendance / summary (mt_user_task_id is null).
|
|
*/
|
|
public function getDaySummary(int $business_id, int $user_id, string $day): ?MtDailyReport
|
|
{
|
|
return MtDailyReport::forBusiness($business_id)
|
|
->where('user_id', $user_id)
|
|
->whereDate('report_date', $day)
|
|
->whereNull('mt_user_task_id')
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* Dates in range where user submitted at least one task report.
|
|
*/
|
|
public function getSubmittedDays(int $business_id, int $user_id, int $days = 14): array
|
|
{
|
|
$start = Carbon::today()->subDays($days);
|
|
|
|
return MtDailyReport::forBusiness($business_id)
|
|
->where('user_id', $user_id)
|
|
->where('report_date', '>=', $start->format('Y-m-d'))
|
|
->whereNotNull('submitted_at')
|
|
->distinct()
|
|
->pluck('report_date')
|
|
->map(fn ($d) => Carbon::parse($d)->format('Y-m-d'))
|
|
->toArray();
|
|
}
|
|
|
|
public function taskStatusOptions(): array
|
|
{
|
|
return [
|
|
3 => __('managementtools::lang.task_in_progress'),
|
|
5 => __('managementtools::lang.task_completed'),
|
|
4 => __('managementtools::lang.task_beyond_ability'),
|
|
];
|
|
}
|
|
|
|
public function userStatusOptions(): array
|
|
{
|
|
return [
|
|
3 => __('managementtools::lang.status_present'),
|
|
1 => __('managementtools::lang.status_absent'),
|
|
2 => __('managementtools::lang.status_leave'),
|
|
4 => __('managementtools::lang.status_mission'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Tasks available for linking to a slot on a given calendar date.
|
|
*/
|
|
public function getTasksForUserOnDate(int $business_id, int $user_id, string $date): Collection
|
|
{
|
|
return \Modules\ManagementTools\Entities\MtUserTask::forBusiness($business_id)
|
|
->where('user_id', $user_id)
|
|
->assignable()
|
|
->where(function ($q) use ($date) {
|
|
$q->whereHas('timeLines', fn ($tl) => $tl->whereDate('starts_at', $date))
|
|
->orWhereDate('due_date', $date)
|
|
->orWhereDate('created_at', $date);
|
|
})
|
|
->orderBy('title')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Build slot => report map for the workday (includes empty slots).
|
|
*/
|
|
public function buildSlotReportMap(int $business_id, int $user_id, string $day): array
|
|
{
|
|
[$start, $end] = $this->getWorkdayBounds($day);
|
|
$slots = $this->generateTimeSlotsForUser($business_id, $user_id, $start, $end, $day);
|
|
$reports = $this->getUserSlotReports($business_id, $user_id, $start, $end);
|
|
|
|
$map = [];
|
|
foreach ($slots as $slot) {
|
|
$map[$slot] = $reports->get($slot);
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
public function getUserReportSettings(int $business_id, int $user_id): MtUserReportSetting
|
|
{
|
|
return MtUserReportSetting::resolveForUser($business_id, $user_id);
|
|
}
|
|
}
|