ultimatepos/Modules/ManagementTools/Utils/DailyReportUtil.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);
}
}