Adding delay and penalty calculations
This commit is contained in:
@@ -18,6 +18,7 @@ use Morilog\Jalali\Jalalian;
|
||||
use GuzzleHttp\Client;
|
||||
use App\Models\Tasks;
|
||||
use App\Models\Pivot_Tasks;
|
||||
use App\Services\LatePenaltyService;
|
||||
|
||||
|
||||
class AdminController extends Controller
|
||||
@@ -283,6 +284,9 @@ class AdminController extends Controller
|
||||
$hours = intdiv($totalSeconds, 3600);
|
||||
$minutes = intdiv($totalSeconds % 3600, 60);
|
||||
$user->totaltime = sprintf('%02d:%02d', $hours, $minutes);
|
||||
[$jMonthStart, $jMonthEnd] = app(LatePenaltyService::class)->jalaliMonthBounds($now);
|
||||
$user->monthly_late_fine = app(LatePenaltyService::class)
|
||||
->monthlyTotalFine($user->user_name, $jMonthStart, $jMonthEnd);
|
||||
$user->working = 0;
|
||||
|
||||
$openForUser = $openAttendances->get($user->user_name, collect());
|
||||
@@ -435,6 +439,7 @@ class AdminController extends Controller
|
||||
$attendance->exited_at = $nendt ? $date . ' ' . $nendt : null;
|
||||
$attendance->duration = $nendt !== null ? $attendance->duration($nstartt, $nendt) : null;
|
||||
$attendance->save();
|
||||
app(LatePenaltyService::class)->syncAttendance($attendance);
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#6aff00ff',
|
||||
"text" => 'تغییرات با موفقیت ذخیره شد.✔️'
|
||||
@@ -461,6 +466,7 @@ class AdminController extends Controller
|
||||
$attendance->duration = $nendt ? $attendance->duration($nstartt, $nendt) : null;
|
||||
|
||||
$attendance->save();
|
||||
app(LatePenaltyService::class)->syncAttendance($attendance);
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#6aff00ff',
|
||||
"text" => 'تغییرات با موفقیت ذخیره شد.✔️'
|
||||
@@ -568,6 +574,7 @@ class AdminController extends Controller
|
||||
$record->duration = $duration ?? $record->duration;
|
||||
$record->requested = '';
|
||||
$record->save();
|
||||
app(LatePenaltyService::class)->syncAttendance($record);
|
||||
|
||||
Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}");
|
||||
$this->decrementUserRequestCount($data->userid);
|
||||
@@ -602,6 +609,7 @@ class AdminController extends Controller
|
||||
? $attendance->duration($starttime, $endtime)
|
||||
: null;
|
||||
$attendance->save();
|
||||
app(LatePenaltyService::class)->syncAttendance($attendance);
|
||||
$create_requests_key = "att-tempreq:creates:{$data->userid}";
|
||||
Redis::connection()->lrem($create_requests_key, 1, json_encode($data));
|
||||
$this->decrementUserRequestCount($data->userid);
|
||||
|
||||
@@ -19,6 +19,7 @@ use App\Models\User;
|
||||
use App\Models\Log;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\ai;
|
||||
use App\Services\LatePenaltyService;
|
||||
|
||||
|
||||
class AttendanceController extends Controller
|
||||
@@ -294,26 +295,9 @@ class AttendanceController extends Controller
|
||||
->latest('entered_at')
|
||||
->first();
|
||||
|
||||
if ($attendance) {
|
||||
// خروج
|
||||
//end task
|
||||
if (!empty($user->active_task)) {
|
||||
$taskDu = TaskDuration::where('user_id', $user->id)->whereNull('end')->whereNull('duration')->get();
|
||||
if ($taskDu) {
|
||||
foreach ($taskDu as $item) {
|
||||
$fakeRequest = Request::create(
|
||||
'/active_task', // مسیر فرضی (مهم نیست)
|
||||
'POST',
|
||||
['task_id' => $item->task_id] // شناسه تسک
|
||||
);
|
||||
if ($item->end == null) {
|
||||
(new TaskController())->activeTask($fakeRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
$user->active_task = null;
|
||||
$user->save();
|
||||
}
|
||||
if ($attendance) {
|
||||
// خروج — همه تسکهای باز کاربر بسته شود
|
||||
app(TaskController::class)->endOpenDurationsForUser($user);
|
||||
|
||||
//end break
|
||||
if ($break = AttendanceBreak::where('attendance_id', $attendance->id)->whereNull('break_end')->latest('break_start')->first()) {
|
||||
@@ -322,6 +306,7 @@ class AttendanceController extends Controller
|
||||
$break->save();
|
||||
}
|
||||
$break = AttendanceBreak::where('attendance_id', $attendance->id)->sum('duration');
|
||||
$attendance->exited_at = Carbon::now()->format('Y-m-d H:i:s');
|
||||
$start = Carbon::parse($attendance->entered_at)->format('H:i:s');
|
||||
$end = Carbon::parse($attendance->exited_at)->format('H:i:s');
|
||||
$duration = $attendance->duration($start, $end);
|
||||
@@ -330,10 +315,9 @@ class AttendanceController extends Controller
|
||||
$breakSec = strtotime("1970-01-01 " . $break) - strtotime("1970-01-01 00:00:00");
|
||||
$durationSec = strtotime("1970-01-01 " . $duration) - strtotime("1970-01-01 00:00:00");
|
||||
$durationSec -= $breakSec;
|
||||
$duration = gmdate("H:i:s", $durationSec);
|
||||
$duration = gmdate("H:i:s", max(0, $durationSec));
|
||||
}
|
||||
|
||||
$attendance->exited_at = Carbon::now()->format('Y-m-d H:i:s');
|
||||
$attendance->exit_location = ['lat' => $userLat, 'lng' => $userLng];
|
||||
$attendance->ip_exit = $userLng;
|
||||
$attendance->distance_exit = $request->input('distance');
|
||||
@@ -359,6 +343,8 @@ class AttendanceController extends Controller
|
||||
$attendance->office_location = ['lat' => $officeLat, 'lng' => $officeLng];
|
||||
$attendance->save();
|
||||
|
||||
app(LatePenaltyService::class)->syncAttendance($attendance);
|
||||
|
||||
$this->send($user->name, $attendance, 'attendance');
|
||||
|
||||
return redirect()->back()->with('message', [
|
||||
@@ -648,6 +634,9 @@ class AttendanceController extends Controller
|
||||
$user->email = $request->email;
|
||||
$user->number = $request->number;
|
||||
$user->chatid = $request->chatid;
|
||||
$user->birthday = $request->filled('birthday')
|
||||
? $this->fa2en_numbers($request->birthday)
|
||||
: null;
|
||||
|
||||
// اگر کاربر ادمین باشه، میتونه نقش رو تغییر بده
|
||||
if (!empty($_COOKIE['ad'])) {
|
||||
|
||||
@@ -21,6 +21,93 @@ 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 = [];
|
||||
@@ -167,6 +254,28 @@ class TaskController extends Controller
|
||||
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;
|
||||
}
|
||||
|
||||
if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $value, $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
|
||||
try {
|
||||
return Carbon::parse($value)->format('Y-m-d');
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function buildTasksPayload($tasks, array $usersById = []): array
|
||||
{
|
||||
$payload = [];
|
||||
@@ -175,8 +284,8 @@ class TaskController extends Controller
|
||||
'id' => $task->id,
|
||||
'title' => $task->title,
|
||||
'description' => $task->description,
|
||||
'start_date' => $task->start_date,
|
||||
'target_date' => $task->target_date,
|
||||
'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,
|
||||
@@ -221,8 +330,8 @@ class TaskController extends Controller
|
||||
'id' => $task->id,
|
||||
'title' => $task->title,
|
||||
'description' => $task->description,
|
||||
'start_date' => $task->start_date,
|
||||
'target_date' => $task->target_date,
|
||||
'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,
|
||||
@@ -516,6 +625,18 @@ class TaskController extends Controller
|
||||
$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();
|
||||
|
||||
@@ -567,9 +688,25 @@ class TaskController extends Controller
|
||||
}
|
||||
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
||||
|
||||
$data = $request->all();
|
||||
$data['created_by'] = $user->id;
|
||||
$data['assignees'] = !empty($data['assignees']) ? json_encode($data['assignees']) : null;
|
||||
$startDate = $this->normalizeTaskDate($request->input('start_date'));
|
||||
if (!$startDate) {
|
||||
return back()->withInput()->with('message', [
|
||||
'color' => 'red',
|
||||
'text' => 'تاریخ شروع الزامی است. لطفاً از تقویم انتخاب کنید.',
|
||||
]);
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
@@ -580,16 +717,16 @@ class TaskController extends Controller
|
||||
$message_admin .= !empty($app) ? "\nاپ {$app->name_fa}" : null;
|
||||
$message_admin .= !empty($deadline) ? "\nددلاین {$deadline}" : null;
|
||||
$message_admin .= !empty($data['start_date']) ? "\nتاریخ شروع {$data['start_date']}" : null;
|
||||
$message_admin .= !empty($data['state_fa']) ? "\nوضعیت: {$data['state_fa']}" : 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($data['assignees'])) {
|
||||
if (!empty($request->assignees)) {
|
||||
foreach ($request->assignees as $assignee) {
|
||||
$user = User::where('id', $assignee)->first();
|
||||
$message_admin .= "\n$user->name";
|
||||
if ($user && $user->bale !== null) {
|
||||
$this->send_bale($user->bale, $message);
|
||||
$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اختصاص یافت.";
|
||||
@@ -628,50 +765,18 @@ class TaskController extends Controller
|
||||
|
||||
// اگر رکورد باز داریم => پایان
|
||||
if (!empty($taskDU) && ($taskDU->start !== null && $taskDU->end == null)) {
|
||||
// dd('in if');
|
||||
|
||||
$attendance = new Attendance();
|
||||
$s = Carbon::parse($taskDU->start)->format('H:i:s');
|
||||
$e = Carbon::parse(now())->format('H:i:s');
|
||||
$duration = $taskDU->duration;
|
||||
if ($e > $s) {
|
||||
$diff = $attendance->duration($s, $e);
|
||||
if ($duration !== null) {
|
||||
[$h1, $m1, $s1] = explode(':', $duration);
|
||||
$durationSeconds = $h1 * 3600 + $m1 * 60 + $s1;
|
||||
|
||||
[$h2, $m2, $s2] = explode(':', $diff);
|
||||
$diffSeconds = $h2 * 3600 + $m2 * 60 + $s2;
|
||||
|
||||
$totalSeconds = $durationSeconds + $diffSeconds;
|
||||
|
||||
$hours = floor($totalSeconds / 3600);
|
||||
$minutes = floor(($totalSeconds % 3600) / 60);
|
||||
$seconds = $totalSeconds % 60;
|
||||
|
||||
$duration = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
|
||||
} else {
|
||||
$duration = $diff;
|
||||
}
|
||||
$taskDU->end = now();
|
||||
$taskDU->duration = $duration;
|
||||
$user->active_task = null;
|
||||
$taskDU->save();
|
||||
$user->save();
|
||||
$this->send_level1_admins("پایان تسک: «{$task->title}»\nتوسط: {$user->name}\nمدت این نوبت: {$duration}");
|
||||
return response()->json([
|
||||
'action' => 'ended',
|
||||
'status' => true,
|
||||
'duration' => $duration,
|
||||
'start' => $taskDU->start,
|
||||
'end' => $taskDU->end,
|
||||
'finished_at' => $this->resolveTaskFinishedAtByTaskId($task->id),
|
||||
]);
|
||||
} else
|
||||
return response()->json([
|
||||
'action' => 'end',
|
||||
'status' => false
|
||||
]);
|
||||
$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([
|
||||
@@ -695,6 +800,80 @@ class TaskController extends Controller
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user