1013 lines
36 KiB
PHP
Executable File
1013 lines
36 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\App;
|
|
use App\Models\TaskComment;
|
|
use App\Models\TaskDuration;
|
|
use http\Env\Response;
|
|
use http\Message;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Http\Request;
|
|
use App\Models\Pivot_Tasks;
|
|
use App\Models\Attendance;
|
|
use GuzzleHttp\Client;
|
|
use App\Models\Tasks;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Carbon\CarbonInterval;
|
|
|
|
|
|
class TaskController extends Controller
|
|
{
|
|
/**
|
|
* بستن همه نوبتهای باز تسک برای یک کاربر (مثلاً هنگام خروج)
|
|
*/
|
|
public function endOpenDurationsForUser(User $user, $endedAt = null): int
|
|
{
|
|
$endedAt = $endedAt ? Carbon::parse($endedAt) : Carbon::now();
|
|
$openItems = TaskDuration::query()
|
|
->where('user_id', $user->id)
|
|
->whereNotNull('start')
|
|
->whereNull('end')
|
|
->get();
|
|
|
|
$closed = 0;
|
|
foreach ($openItems as $item) {
|
|
$this->closeDurationRecord($item, $endedAt);
|
|
$closed++;
|
|
|
|
$taskTitle = Tasks::where('id', $item->task_id)->value('title') ?? ('#' . $item->task_id);
|
|
$this->send_level1_admins(
|
|
"پایان خودکار تسک (خروج): «{$taskTitle}»\nتوسط: {$user->name}\nمدت این نوبت: {$item->duration}"
|
|
);
|
|
}
|
|
|
|
if ($closed > 0 || !empty($user->active_task)) {
|
|
$user->active_task = null;
|
|
$user->save();
|
|
}
|
|
|
|
return $closed;
|
|
}
|
|
|
|
private function closeDurationRecord(TaskDuration $taskDU, Carbon $endedAt): TaskDuration
|
|
{
|
|
$start = Carbon::parse($taskDU->start);
|
|
$seconds = max(0, (int) $start->diffInSeconds($endedAt, false));
|
|
$diff = sprintf(
|
|
'%02d:%02d:%02d',
|
|
intdiv($seconds, 3600),
|
|
intdiv($seconds % 3600, 60),
|
|
$seconds % 60
|
|
);
|
|
|
|
$duration = $taskDU->duration;
|
|
if ($duration !== null && $duration !== '') {
|
|
[$h1, $m1, $s1] = array_map('intval', explode(':', $duration));
|
|
$durationSeconds = $h1 * 3600 + $m1 * 60 + $s1;
|
|
$totalSeconds = $durationSeconds + $seconds;
|
|
$duration = sprintf(
|
|
'%02d:%02d:%02d',
|
|
intdiv($totalSeconds, 3600),
|
|
intdiv($totalSeconds % 3600, 60),
|
|
$totalSeconds % 60
|
|
);
|
|
} else {
|
|
$duration = $diff;
|
|
}
|
|
|
|
$taskDU->end = $endedAt;
|
|
$taskDU->duration = $duration;
|
|
$taskDU->save();
|
|
|
|
return $taskDU;
|
|
}
|
|
|
|
private function secondsBetween(?string $start, ?string $end): int
|
|
{
|
|
if (!$start || !$end) {
|
|
return 0;
|
|
}
|
|
|
|
$s = Carbon::parse($start);
|
|
$e = Carbon::parse($end);
|
|
return max(0, (int) $s->diffInSeconds($e, false));
|
|
}
|
|
|
|
private function formatSeconds(int $seconds): string
|
|
{
|
|
$seconds = max(0, $seconds);
|
|
|
|
return sprintf(
|
|
'%02d:%02d:%02d',
|
|
intdiv($seconds, 3600),
|
|
intdiv($seconds % 3600, 60),
|
|
$seconds % 60
|
|
);
|
|
}
|
|
|
|
private function taskChangeMessage(Tasks $task, array $before, array $after, ?User $actor): string
|
|
{
|
|
$lines = [];
|
|
$actorName = $actor?->name ?? 'نامشخص';
|
|
|
|
$titleBefore = (string)($before['title'] ?? '');
|
|
$titleAfter = (string)($after['title'] ?? $task->title ?? '');
|
|
|
|
$lines[] = "ویرایش تسک: «{$titleAfter}»";
|
|
$lines[] = "توسط: {$actorName}";
|
|
|
|
$appBefore = $before['app'] ?? null;
|
|
$appAfter = $after['app'] ?? $task->app ?? null;
|
|
$a1 = App::find($appBefore)?->name_fa ?? ($appBefore ?? '-');
|
|
$a2 = App::find($appAfter)?->name_fa ?? ($appAfter ?? '-');
|
|
$lines[] = ($appBefore != $appAfter) ? "اپ: {$a1} ← {$a2}" : "اپ: {$a2}";
|
|
|
|
$lines[] = (($before['title'] ?? null) !== ($after['title'] ?? null))
|
|
? "عنوان: {$titleBefore} ← {$titleAfter}"
|
|
: "عنوان: {$titleAfter}";
|
|
|
|
$priorityFa = [
|
|
'none' => 'ندارد',
|
|
'low' => 'کم',
|
|
'medium' => 'عادی',
|
|
'high' => 'زیاد',
|
|
'urgent' => 'فوری',
|
|
];
|
|
$p1 = $before['priority'] ?? null;
|
|
$p2 = $after['priority'] ?? null;
|
|
$lines[] = ($p1 !== $p2)
|
|
? "اولویت: " . ($priorityFa[$p1] ?? ($p1 ?? '-')) . " ← " . ($priorityFa[$p2] ?? ($p2 ?? '-'))
|
|
: "اولویت: " . ($priorityFa[$p2] ?? ($p2 ?? '-'));
|
|
|
|
$stateFa = [
|
|
1 => 'انباشته',
|
|
2 => 'برای انجام',
|
|
3 => 'درحال انجام',
|
|
4 => 'پایان یافته',
|
|
];
|
|
$s1 = (int)($before['state'] ?? 0);
|
|
$s2 = (int)($after['state'] ?? 0);
|
|
$lines[] = ($s1 !== $s2 && $s2)
|
|
? "وضعیت: " . ($stateFa[$s1] ?? ($before['state'] ?? '-')) . " ← " . ($stateFa[$s2] ?? ($after['state'] ?? '-'))
|
|
: "وضعیت: " . ($stateFa[$s2] ?? ($after['state'] ?? '-'));
|
|
|
|
$d1 = \App\Support\JalaliHelper::format($before['start_date'] ?? null, 'Y/m/d', '-');
|
|
$d2 = \App\Support\JalaliHelper::format($after['start_date'] ?? null, 'Y/m/d', '-');
|
|
$lines[] = ($d1 !== $d2)
|
|
? "شروع: {$d1} ← {$d2}"
|
|
: "شروع: {$d2}";
|
|
|
|
$t1 = \App\Support\JalaliHelper::format($before['target_date'] ?? null, 'Y/m/d', '-');
|
|
$t2 = \App\Support\JalaliHelper::format($after['target_date'] ?? null, 'Y/m/d', '-');
|
|
$lines[] = ($t1 !== $t2)
|
|
? "ددلاین: {$t1} ← {$t2}"
|
|
: "ددلاین: {$t2}";
|
|
|
|
$desc1 = trim((string)($before['description'] ?? ''));
|
|
$desc2 = trim((string)($after['description'] ?? ''));
|
|
$lines[] = ($desc1 !== $desc2)
|
|
? "توضیحات: " . (!empty($desc2) ? 'تغییر کرد' : 'پاک شد')
|
|
: "توضیحات: بدون تغییر";
|
|
|
|
$aBefore = $before['assignees'] ?? null;
|
|
$aAfter = $after['assignees'] ?? null;
|
|
$arr1 = is_string($aBefore) ? json_decode($aBefore, true) : $aBefore;
|
|
$arr2 = is_string($aAfter) ? json_decode($aAfter, true) : $aAfter;
|
|
$arr1 = is_array($arr1) ? array_map('strval', $arr1) : [];
|
|
$arr2 = is_array($arr2) ? array_map('strval', $arr2) : [];
|
|
sort($arr1);
|
|
sort($arr2);
|
|
$names1 = User::whereIn('id', $arr1)->pluck('name')->all();
|
|
$names2 = User::whereIn('id', $arr2)->pluck('name')->all();
|
|
$lines[] = ($arr1 !== $arr2)
|
|
? "مسئولین: " . (!empty($names1) ? implode('، ', $names1) : '-') . " ← " . (!empty($names2) ? implode('، ', $names2) : '-')
|
|
: "مسئولین: " . (!empty($names2) ? implode('، ', $names2) : '-');
|
|
|
|
return implode("\n", $lines);
|
|
}
|
|
|
|
private function sumDurationSeconds($durations, ?int $userId = null): int
|
|
{
|
|
$totalSeconds = 0;
|
|
foreach ($durations as $item) {
|
|
if ($userId !== null && (int)$item->user_id !== $userId) {
|
|
continue;
|
|
}
|
|
if ($item->duration === null) {
|
|
continue;
|
|
}
|
|
try {
|
|
$totalSeconds += CarbonInterval::createFromFormat('H:i:s', $item->duration)->totalSeconds;
|
|
} catch (\Throwable) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return $totalSeconds;
|
|
}
|
|
|
|
private function resolveTaskFinishedAt($durations): ?string
|
|
{
|
|
$latest = collect($durations)
|
|
->pluck('end')
|
|
->filter()
|
|
->map(fn ($value) => Carbon::parse($value))
|
|
->sortDesc()
|
|
->first();
|
|
|
|
return $latest?->toDateTimeString();
|
|
}
|
|
|
|
private function resolveTaskFinishedAtByTaskId(int $taskId): ?string
|
|
{
|
|
return $this->resolveTaskFinishedAt(
|
|
TaskDuration::query()
|
|
->where('task_id', $taskId)
|
|
->whereNotNull('end')
|
|
->get(['end'])
|
|
);
|
|
}
|
|
|
|
private function resolveAssigneeNames(Tasks $task, array $usersById = []): array
|
|
{
|
|
$assignees = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
|
$assignees = is_array($assignees) ? array_map('strval', $assignees) : [];
|
|
|
|
if (empty($assignees)) {
|
|
return [];
|
|
}
|
|
|
|
if (!empty($usersById)) {
|
|
$names = [];
|
|
foreach ($assignees as $id) {
|
|
if (!empty($usersById[$id])) {
|
|
$names[] = $usersById[$id];
|
|
}
|
|
}
|
|
|
|
return $names;
|
|
}
|
|
|
|
return User::whereIn('id', $assignees)->pluck('name')->all();
|
|
}
|
|
|
|
private function normalizeTaskDate(?string $value): ?string
|
|
{
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
|
|
$value = trim($value);
|
|
if ($value === '' || str_starts_with($value, '0000-00-00')) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $value, $m)) {
|
|
$carbon = Carbon::createFromFormat('Y-m-d', $m[1])->startOfDay();
|
|
} else {
|
|
$carbon = Carbon::parse($value);
|
|
}
|
|
|
|
if ($carbon->year < 1621 || $carbon->year > 2500) {
|
|
return null;
|
|
}
|
|
|
|
return $carbon->format('Y-m-d');
|
|
} catch (\Throwable $e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function buildTasksPayload($tasks, array $usersById = []): array
|
|
{
|
|
$payload = [];
|
|
foreach ($tasks as $task) {
|
|
$payload[$task->id] = [
|
|
'id' => $task->id,
|
|
'title' => $task->title,
|
|
'description' => $task->description,
|
|
'start_date' => $this->normalizeTaskDate($task->start_date ? (string) $task->start_date : null),
|
|
'target_date' => $this->normalizeTaskDate($task->target_date ? (string) $task->target_date : null),
|
|
'finished_at' => $task->finished_at ?? null,
|
|
'priority' => $task->priority,
|
|
'state' => $task->state,
|
|
'app' => $task->app,
|
|
'active' => (bool)$task->active,
|
|
'creator_name' => $task->creator_name,
|
|
'created_at' => $task->created_at,
|
|
'assignee_names' => $task->assignee_names ?? $this->resolveAssigneeNames($task, $usersById),
|
|
'total_duration' => $task->total_duration ?? '00:00:00',
|
|
'comments' => [],
|
|
'duration' => [],
|
|
];
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
private function userCanAccessTask(Tasks $task, User $user): bool
|
|
{
|
|
if ($user->is_admin == 1) {
|
|
return true;
|
|
}
|
|
|
|
$assignees = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
|
$assignees = is_array($assignees) ? array_map('strval', $assignees) : [];
|
|
|
|
return (int)$task->created_by === (int)$user->id
|
|
|| in_array((string)$user->id, $assignees, true);
|
|
}
|
|
|
|
private function buildTaskDetailPayload(Tasks $task, bool $isAdmin, User $user): array
|
|
{
|
|
$activeQuery = TaskDuration::query()
|
|
->where('task_id', $task->id)
|
|
->whereNull('end');
|
|
|
|
if (!$isAdmin) {
|
|
$activeQuery->where('user_id', $user->id);
|
|
}
|
|
|
|
return [
|
|
'id' => $task->id,
|
|
'title' => $task->title,
|
|
'description' => $task->description,
|
|
'start_date' => $this->normalizeTaskDate($task->start_date ? (string) $task->start_date : null),
|
|
'target_date' => $this->normalizeTaskDate($task->target_date ? (string) $task->target_date : null),
|
|
'finished_at' => $this->resolveTaskFinishedAtByTaskId($task->id),
|
|
'priority' => $task->priority,
|
|
'state' => $task->state,
|
|
'app' => $task->app,
|
|
'active' => $activeQuery->exists(),
|
|
'creator_name' => $task->creator?->name ?? 'نامشخص',
|
|
'created_at' => $task->created_at,
|
|
'assignee_names' => $this->resolveAssigneeNames($task),
|
|
'duration' => $task->duration->map(fn ($d) => [
|
|
'id' => $d->id,
|
|
'task_id' => $d->task_id,
|
|
'user_id' => $d->user_id,
|
|
'start' => $d->start,
|
|
'end' => $d->end,
|
|
'duration' => $d->duration,
|
|
])->values()->all(),
|
|
'comments' => $task->comments->map(fn ($c) => [
|
|
'id' => $c->id,
|
|
'body' => $c->body,
|
|
'created_at' => $c->created_at,
|
|
'user' => $c->user ? ['name' => $c->user->name] : null,
|
|
])->values()->all(),
|
|
];
|
|
}
|
|
|
|
private function loadTasksPageData(User $user): array
|
|
{
|
|
$isAdmin = $user->is_admin == 1;
|
|
|
|
$query = Tasks::query()
|
|
->select([
|
|
'id',
|
|
'title',
|
|
'description',
|
|
'start_date',
|
|
'target_date',
|
|
'priority',
|
|
'state',
|
|
'app',
|
|
'created_by',
|
|
'assignees',
|
|
'created_at',
|
|
'updated_at',
|
|
])
|
|
->with([
|
|
'appName:id,name_fa,type',
|
|
'creator:id,name',
|
|
])
|
|
->orderByRaw('target_date IS NULL')
|
|
->orderBy('target_date', 'asc')
|
|
->orderBy('updated_at', 'desc');
|
|
|
|
if (!$isAdmin) {
|
|
$query->where(function ($q) use ($user) {
|
|
$q->where('created_by', $user->id)
|
|
->orWhereJsonContains('assignees', (string)$user->id);
|
|
});
|
|
}
|
|
|
|
$tasks = $query->get();
|
|
$taskIds = $tasks->pluck('id');
|
|
|
|
$assigneeIds = [];
|
|
foreach ($tasks as $task) {
|
|
$ids = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
|
if (is_array($ids)) {
|
|
$assigneeIds = array_merge($assigneeIds, array_map('strval', $ids));
|
|
}
|
|
}
|
|
$usersById = !empty($assigneeIds)
|
|
? User::whereIn('id', array_unique($assigneeIds))
|
|
->get()
|
|
->mapWithKeys(fn ($user) => [(string)$user->id => $user->name])
|
|
->all()
|
|
: [];
|
|
|
|
if ($taskIds->isEmpty()) {
|
|
return [
|
|
'tasks' => $tasks,
|
|
'tasksPayload' => [],
|
|
'assigneeFilterOptions' => [],
|
|
];
|
|
}
|
|
|
|
$activeQuery = TaskDuration::query()
|
|
->select(['task_id'])
|
|
->whereIn('task_id', $taskIds)
|
|
->whereNull('end');
|
|
|
|
if (!$isAdmin) {
|
|
$activeQuery->where('user_id', $user->id);
|
|
}
|
|
|
|
$activeTaskIds = $activeQuery->pluck('task_id')->flip();
|
|
|
|
$durationQuery = TaskDuration::query()
|
|
->select(['task_id', 'user_id', 'duration', 'end'])
|
|
->whereIn('task_id', $taskIds)
|
|
->whereNotNull('duration');
|
|
|
|
if (!$isAdmin) {
|
|
$durationQuery->where('user_id', $user->id);
|
|
}
|
|
|
|
$durationsByTaskId = $durationQuery->get()->groupBy('task_id');
|
|
|
|
$finishedAtByTaskId = TaskDuration::query()
|
|
->select(['task_id', 'end'])
|
|
->whereIn('task_id', $taskIds)
|
|
->whereNotNull('end')
|
|
->get()
|
|
->groupBy('task_id');
|
|
|
|
foreach ($tasks as $task) {
|
|
$task->active = $activeTaskIds->has($task->id);
|
|
$task->creator_name = $task->creator?->name ?? 'نامشخص';
|
|
$task->assignee_names = $this->resolveAssigneeNames($task, $usersById);
|
|
$ids = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
|
$task->assignee_ids = is_array($ids) ? array_map('strval', $ids) : [];
|
|
$task->finished_at = $this->resolveTaskFinishedAt($finishedAtByTaskId->get($task->id, collect()));
|
|
$totalSeconds = $this->sumDurationSeconds(
|
|
$durationsByTaskId->get($task->id, collect()),
|
|
$isAdmin ? null : $user->id
|
|
);
|
|
$task->total_duration = CarbonInterval::seconds($totalSeconds)->cascade()->format('%H:%I:%S');
|
|
}
|
|
|
|
$assigneeFilterOptions = collect($usersById)
|
|
->map(fn ($name, $id) => ['id' => (string)$id, 'name' => $name])
|
|
->sortBy('name', SORT_NATURAL)
|
|
->values()
|
|
->all();
|
|
|
|
return [
|
|
'tasks' => $tasks,
|
|
'tasksPayload' => $this->buildTasksPayload($tasks, $usersById),
|
|
'assigneeFilterOptions' => $assigneeFilterOptions,
|
|
];
|
|
}
|
|
|
|
// public function tasks()
|
|
// {
|
|
// $user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
// $tasks = $user->tasks;
|
|
// $users = User::get();
|
|
//
|
|
// $state_order = [
|
|
// "c73c2044-ea14-42cb-b08d-cecf3d1fdfb5",
|
|
// "a9ce312f-cfca-491e-8e3d-1975aa6ebc10",
|
|
// "dea1fb57-a281-4fe7-8f3e-ee5567637e35",
|
|
// "770fbe42-99f6-41db-9b9e-773d9cced1a5",
|
|
// "b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0",
|
|
// "d61d14a3-72f7-4f44-894b-efe8b2a75be1",
|
|
// ];
|
|
//
|
|
// $work_item = $tasks->whereNull('parent')->sortBy(fn($t) => array_search($t->state, $state_order));
|
|
// $sub_work_items = $tasks->whereNotNull('parent')->sortBy(fn($t) => array_search($t->state, $state_order));
|
|
//
|
|
// foreach ($work_item as $w) {
|
|
// foreach ($sub_work_items as $t) {
|
|
// foreach ($users as $u) {
|
|
// if ($t->created_by == $u->plain_id)
|
|
// $t->created_by = $u->name;
|
|
// if ($w->created_by == $u->plain_id)
|
|
// $w->created_by = $u->name;
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// return view('tasks.tasks', compact('tasks', 'work_item', 'sub_work_items'));
|
|
// }
|
|
|
|
public function tasks()
|
|
{
|
|
if (!isset($_COOKIE['user_identity'])) {
|
|
return redirect('/')->with('message', [
|
|
'color' => 'red',
|
|
'text' => 'ابتدا باید وارد شوید!'
|
|
]);
|
|
}
|
|
|
|
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
if ($user) {
|
|
['tasks' => $tasks, 'tasksPayload' => $tasksPayload, 'assigneeFilterOptions' => $assigneeFilterOptions] = $this->loadTasksPageData($user);
|
|
$count = $tasks->count();
|
|
|
|
return view('tasks.tasks', [
|
|
'tasks' => (new Tasks())->filterByState($tasks),
|
|
'tasksPayload' => $tasksPayload,
|
|
'assigneeFilterOptions' => $assigneeFilterOptions,
|
|
'count' => $count,
|
|
'user_id' => $user->id,
|
|
'isAdminLevel1' => $user->is_admin == 1,
|
|
'mentionUsers' => User::select('id', 'name', 'user_name')->get(),
|
|
'appsList' => App::select('id', 'name_fa', 'type')->get(),
|
|
'priorityLabels' => [
|
|
'urgent' => 'فوری',
|
|
'high' => 'بالا',
|
|
'medium' => 'متوسط',
|
|
'low' => 'پایین'
|
|
],
|
|
'priorityColors' => [
|
|
'urgent' => 'bg-red-100 text-red-800',
|
|
'high' => 'bg-orange-100 text-orange-800',
|
|
'medium' => 'bg-yellow-100 text-yellow-800',
|
|
'low' => 'bg-green-100 text-green-800'
|
|
],
|
|
|
|
]);
|
|
} else {
|
|
return back()->with('message', [
|
|
'color' => 'red',
|
|
'text' => 'کاربر یافت نشد!'
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function showTask($id)
|
|
{
|
|
|
|
$task = Tasks::with(['comments.user'])->find($id ?? null);
|
|
return view('tasks.create', ['task' => $task]);
|
|
|
|
}
|
|
|
|
public function taskDetailsJson($id)
|
|
{
|
|
if (!isset($_COOKIE['user_identity'])) {
|
|
return response()->json(['error' => 'Unauthorized'], 401);
|
|
}
|
|
|
|
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
if (!$user) {
|
|
return response()->json(['error' => 'User not found'], 404);
|
|
}
|
|
|
|
$isAdmin = $user->is_admin == 1;
|
|
$task = Tasks::query()
|
|
->with([
|
|
'creator:id,name',
|
|
'comments.user:id,name',
|
|
'duration' => function ($q) use ($isAdmin, $user) {
|
|
if (!$isAdmin) {
|
|
$q->where('user_id', $user->id);
|
|
}
|
|
},
|
|
])
|
|
->find($id);
|
|
|
|
if (!$task || !$this->userCanAccessTask($task, $user)) {
|
|
return response()->json(['error' => 'Not found'], 404);
|
|
}
|
|
|
|
return response()->json($this->buildTaskDetailPayload($task, $isAdmin, $user));
|
|
}
|
|
|
|
public function editTask(Request $request)
|
|
{
|
|
// dd($request->all());
|
|
if (!isset($_COOKIE['user_identity'])) {
|
|
return back()->with('message', [
|
|
'color' => 'red',
|
|
'text' => 'ابتدا باید وارد شوید!'
|
|
]);
|
|
}
|
|
$actor = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
|
|
$data = $request->all();
|
|
$task = Tasks::find($data['id'] ?? null);
|
|
if (!$task) {
|
|
return back()->with('message', [
|
|
'color' => 'red',
|
|
'text' => 'وظیفه یافت نشد!'
|
|
]);
|
|
}
|
|
|
|
$before = $task->toArray();
|
|
|
|
$data['updated_by'] = $actor?->id;
|
|
if (array_key_exists('assignees', $data)) {
|
|
$data['assignees'] = !empty($data['assignees']) ? json_encode($data['assignees']) : null;
|
|
} else {
|
|
unset($data['assignees']);
|
|
}
|
|
unset(
|
|
$data['_token'],
|
|
$data['id'],
|
|
$data['start_date_display'],
|
|
$data['target_date_display'],
|
|
$data['completed_at'],
|
|
$data['completed_at_display']
|
|
);
|
|
|
|
if (array_key_exists('start_date', $data)) {
|
|
$data['start_date'] = $this->normalizeTaskDate($data['start_date'] ?: null);
|
|
// خالی فرستادن نباید تاریخ قبلی را پاک کند مگر صریح null نشود
|
|
if ($data['start_date'] === null) {
|
|
unset($data['start_date']);
|
|
}
|
|
}
|
|
if (array_key_exists('target_date', $data)) {
|
|
$normalizedTarget = $this->normalizeTaskDate($data['target_date'] ?: null);
|
|
$data['target_date'] = $normalizedTarget;
|
|
}
|
|
|
|
$task->update($data);
|
|
$after = $task->toArray();
|
|
|
|
$title = $task->title;
|
|
$message = "تسک «{$title}» برای شما بهروزرسانی شد.";
|
|
$message_admin = $this->taskChangeMessage($task, $before, $after, $actor);
|
|
|
|
if (!empty($request->assignees)) {
|
|
foreach ($request->assignees as $assignee) {
|
|
$assigneeUser = User::where('id', $assignee)->first();
|
|
if ($assigneeUser && $assigneeUser->bale !== null) {
|
|
$this->send_bale($assigneeUser->bale, $message);
|
|
}
|
|
}
|
|
}
|
|
// dd($data, $message, $message_admin);
|
|
$this->send_level1_admins($message_admin);
|
|
|
|
return back()->with('message', [
|
|
'color' => 'green',
|
|
'text' => $message
|
|
]);
|
|
}
|
|
|
|
public function createTask()
|
|
{
|
|
return view('tasks.create');
|
|
}
|
|
|
|
public function deleteTask($id)
|
|
{
|
|
Tasks::destroy($id);
|
|
|
|
return response()->json([
|
|
"success" => true,
|
|
"message" => "Task deleted successfully.",
|
|
"task_id" => 45
|
|
]);
|
|
}
|
|
|
|
public function storeTask(Request $request)
|
|
{
|
|
// dd($request->all());
|
|
if (!isset($_COOKIE['user_identity'])) {
|
|
return back()->with('message', [
|
|
'color' => 'red',
|
|
'text' => 'ابتدا باید وارد شوید!'
|
|
]);
|
|
}
|
|
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
|
|
$startDate = $this->normalizeTaskDate($request->input('start_date'))
|
|
?: now()->format('Y-m-d');
|
|
|
|
$data = [
|
|
'title' => $request->input('title'),
|
|
'description' => $request->input('description'),
|
|
'priority' => $request->input('priority', 'none'),
|
|
'state' => $request->input('state', 1),
|
|
'app' => $request->input('app') ?: null,
|
|
'start_date' => $startDate,
|
|
'target_date' => $this->normalizeTaskDate($request->input('target_date')),
|
|
'created_by' => $user->id,
|
|
'assignees' => !empty($request->assignees) ? json_encode($request->assignees) : null,
|
|
];
|
|
|
|
$task = Tasks::create($data);
|
|
|
|
$message = "تسک جدیدی با عنوان {$data['title']} برای شما ایجاد شد.";
|
|
$deadline = \App\Support\JalaliHelper::format($data['target_date'] ?? null, 'Y/m/d', '');
|
|
$deadline = $deadline !== '' ? $deadline : null;
|
|
$startDateJalali = \App\Support\JalaliHelper::format($data['start_date'] ?? null, 'Y/m/d', '');
|
|
$app = App::find($data['app']);
|
|
$message_admin = "ایجاد تسک: «{$data['title']}»";
|
|
$message_admin .= !empty($app) ? "\nاپ {$app->name_fa}" : null;
|
|
$message_admin .= !empty($deadline) ? "\nددلاین {$deadline}" : null;
|
|
$message_admin .= $startDateJalali !== '' ? "\nتاریخ شروع {$startDateJalali}" : null;
|
|
$message_admin .= !empty($request->input('state_fa')) ? "\nوضعیت: {$request->input('state_fa')}" : null;
|
|
$message_admin .= !empty($data['description']) ? "\nبا توضیحات:\n" . $data['description'] : null;
|
|
$message_admin .= !empty($data['assignees']) ? "\nتوسط " . $user->name . " ساخته شد و به" : null;
|
|
|
|
if (!empty($request->assignees)) {
|
|
foreach ($request->assignees as $assignee) {
|
|
$assigneeUser = User::where('id', $assignee)->first();
|
|
$message_admin .= "\n" . ($assigneeUser->name ?? $assignee);
|
|
if ($assigneeUser && $assigneeUser->bale !== null) {
|
|
$this->send_bale($assigneeUser->bale, $message);
|
|
}
|
|
}
|
|
$message_admin .= "\nاختصاص یافت.";
|
|
} else {
|
|
$message_admin .= "\nتوسط " . $user->name . " ساخته شد";
|
|
}
|
|
// dd($data, $message, $message_admin);
|
|
$this->send_level1_admins($message_admin);
|
|
|
|
return redirect('/tasks')->with('message', [
|
|
'color' => 'green',
|
|
'text' => $message
|
|
]);
|
|
}
|
|
|
|
public function activeTask(Request $request)
|
|
{
|
|
// dd($request);
|
|
if (!isset($_COOKIE['user_identity'])) {
|
|
return response()->json(['action' => 'unauthorized', 'status' => false], 401);
|
|
}
|
|
|
|
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
$task = Tasks::find($request->task_id);
|
|
if (!$task) {
|
|
return response()->json([
|
|
'action' => 'not_found',
|
|
'status' => false
|
|
]);
|
|
}
|
|
|
|
$taskDU = TaskDuration::where('user_id', $user->id)
|
|
->where('task_id', $request->task_id)
|
|
->orderBy('id', 'desc')
|
|
->first();
|
|
|
|
// اگر رکورد باز داریم => پایان
|
|
if (!empty($taskDU) && ($taskDU->start !== null && $taskDU->end == null)) {
|
|
$this->closeDurationRecord($taskDU, Carbon::now());
|
|
$user->active_task = null;
|
|
$user->save();
|
|
$this->send_level1_admins("پایان تسک: «{$task->title}»\nتوسط: {$user->name}\nمدت این نوبت: {$taskDU->duration}");
|
|
return response()->json([
|
|
'action' => 'ended',
|
|
'status' => true,
|
|
'duration' => $taskDU->duration,
|
|
'start' => $taskDU->start,
|
|
'end' => $taskDU->end,
|
|
'finished_at' => $this->resolveTaskFinishedAtByTaskId($task->id),
|
|
]);
|
|
} else {
|
|
// شروع => همیشه یک رکورد جدید بساز
|
|
$new_task_duration = TaskDuration::create([
|
|
'task_id' => $task->id,
|
|
'user_id' => $user->id,
|
|
'start' => now(),
|
|
'end' => null,
|
|
]);
|
|
$user->active_task = $task->title;
|
|
$user->save();
|
|
$this->send_level1_admins("شروع تسک: «{$task->title}»\nتوسط: {$user->name}");
|
|
return response()->json([
|
|
'action' => 'start',
|
|
'status' => true,
|
|
'start' => $new_task_duration->start,
|
|
'id' => $new_task_duration->id,
|
|
]);
|
|
}
|
|
return back()->with('message', ['color' => 'red',
|
|
'text' => 'وظیفه یافت نشد✖️']);
|
|
|
|
}
|
|
|
|
public function updateTaskDuration(Request $request)
|
|
{
|
|
if (!isset($_COOKIE['user_identity'])) {
|
|
return response()->json(['status' => false, 'message' => 'unauthorized'], 401);
|
|
}
|
|
|
|
$admin = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
if (!$admin || (int) $admin->is_admin !== 1) {
|
|
return response()->json(['status' => false, 'message' => 'فقط ادمین میتواند زمان را ویرایش کند.'], 403);
|
|
}
|
|
|
|
$request->validate([
|
|
'duration_id' => 'required|integer',
|
|
'start' => 'required|date',
|
|
'end' => 'nullable|date',
|
|
]);
|
|
|
|
$taskDU = TaskDuration::find($request->duration_id);
|
|
if (!$taskDU) {
|
|
return response()->json(['status' => false, 'message' => 'رکورد زمان یافت نشد.'], 404);
|
|
}
|
|
|
|
$start = Carbon::parse($request->start);
|
|
$end = $request->filled('end') ? Carbon::parse($request->end) : null;
|
|
|
|
if ($end && $end->lt($start)) {
|
|
return response()->json([
|
|
'status' => false,
|
|
'message' => 'زمان پایان نمیتواند قبل از شروع باشد.',
|
|
], 422);
|
|
}
|
|
|
|
$taskDU->start = $start;
|
|
$taskDU->end = $end;
|
|
$taskDU->duration = $end ? $this->formatSeconds($this->secondsBetween($start->toDateTimeString(), $end->toDateTimeString())) : null;
|
|
$taskDU->save();
|
|
|
|
// اگر این نوبت هنوز باز است و مال همان کاربر فعال است، active_task را همگام کن
|
|
$owner = User::find($taskDU->user_id);
|
|
if ($owner) {
|
|
$stillOpen = TaskDuration::query()
|
|
->where('user_id', $owner->id)
|
|
->whereNull('end')
|
|
->exists();
|
|
if (!$stillOpen) {
|
|
$owner->active_task = null;
|
|
$owner->save();
|
|
}
|
|
}
|
|
|
|
$task = Tasks::find($taskDU->task_id);
|
|
$this->send_level1_admins(
|
|
"ویرایش زمان تسک: «" . ($task->title ?? $taskDU->task_id) . "»\n"
|
|
. "توسط ادمین: {$admin->name}\n"
|
|
. "شروع: " . $start->format('Y-m-d H:i') . "\n"
|
|
. "پایان: " . ($end ? $end->format('Y-m-d H:i') : 'باز') . "\n"
|
|
. "مدت: " . ($taskDU->duration ?? '-')
|
|
);
|
|
|
|
return response()->json([
|
|
'status' => true,
|
|
'message' => 'زمان با موفقیت ذخیره شد.',
|
|
'duration' => [
|
|
'id' => $taskDU->id,
|
|
'task_id' => $taskDU->task_id,
|
|
'user_id' => $taskDU->user_id,
|
|
'start' => $taskDU->start,
|
|
'end' => $taskDU->end,
|
|
'duration' => $taskDU->duration,
|
|
],
|
|
'finished_at' => $this->resolveTaskFinishedAtByTaskId($taskDU->task_id),
|
|
]);
|
|
}
|
|
|
|
|
|
public function addTask(Request $request)
|
|
{
|
|
if (isset($_COOKIE['user_identity'])) {
|
|
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
} else {
|
|
return back()->with('message', [
|
|
'color' => 'red',
|
|
'text' => 'auth failed!'
|
|
]);
|
|
}
|
|
$request->validate([
|
|
'title' => 'required',
|
|
'description' => 'required',
|
|
'assignees' => 'required',
|
|
'start_date' => 'required',
|
|
'target_date' => 'required',
|
|
'priority' => 'required',
|
|
'description_html' => 'required',
|
|
'completed_at' => 'required',
|
|
'state' => 'required',
|
|
'created_at' => 'required',
|
|
'updated_at' => 'required',
|
|
'app' => 'required',
|
|
'task_type' => 'required',
|
|
]);
|
|
$data = $request->all();
|
|
$data['user_id'] = $user->id;
|
|
|
|
Tasks::create([$data]);
|
|
return back()->with('message', [
|
|
'color' => 'green',
|
|
'text' => 'وظیفه ایجاد شد.'
|
|
]);
|
|
}
|
|
|
|
public function storeComment(Request $request, $id)
|
|
{
|
|
if (!isset($_COOKIE['user_identity'])) {
|
|
return back()->with('message', [
|
|
'color' => 'red',
|
|
'text' => 'ابتدا باید وارد شوید!'
|
|
]);
|
|
}
|
|
|
|
$actor = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
$task = Tasks::find($id ?? null);
|
|
if (!$task) {
|
|
return back()->with('message', [
|
|
'color' => 'red',
|
|
'text' => 'وظیفه یافت نشد!'
|
|
]);
|
|
}
|
|
|
|
$request->validate([
|
|
'body' => 'required|string',
|
|
]);
|
|
|
|
$body = trim((string)$request->input('body'));
|
|
|
|
preg_match_all('/@([\p{L}\p{N}_.-]+)/u', $body, $matches);
|
|
$usernames = array_values(array_unique($matches[1] ?? []));
|
|
|
|
$mentionedUsers = !empty($usernames)
|
|
? User::whereIn('user_name', $usernames)->get()
|
|
: collect();
|
|
|
|
$mentionedIds = $mentionedUsers->pluck('id')->values()->all();
|
|
|
|
TaskComment::create([
|
|
'task_id' => $task->id,
|
|
'user_id' => $actor->id,
|
|
'body' => $body,
|
|
'mentioned_user_ids' => !empty($mentionedIds) ? $mentionedIds : null,
|
|
]);
|
|
|
|
$text = "کامنت جدید روی تسک «{$task->title}»\n"
|
|
. "توسط: {$actor->name}\n\n"
|
|
. $body;
|
|
|
|
foreach ($mentionedUsers as $mu) {
|
|
if ($mu->id === $actor->id) {
|
|
continue;
|
|
}
|
|
if ($mu->bale) {
|
|
$this->send_bale($mu->bale, $text);
|
|
}
|
|
}
|
|
|
|
$this->send_level1_admins($text);
|
|
|
|
return back()->with('message', [
|
|
'color' => 'green',
|
|
'text' => 'کامنت ثبت شد.'
|
|
]);
|
|
}
|
|
|
|
|
|
public function webhookPlane(Request $request)
|
|
{
|
|
$d = $request->all();
|
|
|
|
// فقط وقتی تغییر state هست
|
|
if (isset($d['event'], $d['action'], $d['data']['name'], $d['data']['state']['name'])) {
|
|
$msg = "Event: {$d['event']}\nAction: {$d['action']}\nName: {$d['data']['name']}\nNew Status: {$d['data']['state']['name']}";
|
|
|
|
// ارسال به تلگرام
|
|
Http::get("https://api.wwwapp.ir/telegran_send.php", [
|
|
'botToken' => "7467099619:AAF2V6yYcbAb8oSIRSCyH5i2GKfdWVO3a7k",
|
|
'chatId' => "-4742642674",
|
|
'message' => $msg,
|
|
]);
|
|
}
|
|
|
|
// اگر خواستی ثبت در دیتابیس هم اضافه کنی
|
|
// DB::table('tst')->insert([
|
|
// 'data' => json_encode($d, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
|
|
// ]);
|
|
|
|
return response()->json(['status' => 'ok'], 200);
|
|
}
|
|
|
|
|
|
private function send_bale($chat_id, $message)
|
|
{
|
|
try {
|
|
Http::withoutVerifying()->post('https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [
|
|
"chat_id" => $chat_id,
|
|
'text' => $message,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
}
|
|
}
|
|
}
|