diff --git a/app/Filament/Resources/User/ExamUserResource.php b/app/Filament/Resources/User/ExamUserResource.php index 387597eb..36cefed9 100644 --- a/app/Filament/Resources/User/ExamUserResource.php +++ b/app/Filament/Resources/User/ExamUserResource.php @@ -8,6 +8,7 @@ use App\Models\Exam; use App\Models\ExamUser; use App\Repositories\ExamRepository; use App\Repositories\HitAndRunRepository; +use Carbon\Carbon; use Filament\Forms; use Filament\Resources\Form; use Filament\Resources\Resource; @@ -94,8 +95,34 @@ class ExamUserResource extends Resource $rep->avoidExamUserBulk(['id' => $idArr], Auth::user()); }) ->deselectRecordsAfterCompletion() + ->requiresConfirmation() ->label(__('admin.resources.exam_user.bulk_action_avoid_label')) - ->icon('heroicon-o-x') + ->icon('heroicon-o-x'), + + Tables\Actions\BulkAction::make('UpdateEnd') + ->form([ + Forms\Components\DateTimePicker::make('end') + ->required() + ->label(__('label.end')) + , + Forms\Components\Textarea::make('reason') + ->label(__('label.reason')) + , + ]) + ->action(function (Collection $records, array $data) { + $end = Carbon::parse($data['end']); + $rep = new ExamRepository(); + foreach ($records as $record) { + if ($end->isAfter($record->begin)) { + $rep->updateExamUserEnd($record, $end, $data['reason'] ?? ''); + } else { + do_log(sprintf("examUser: %d end: %s is before begin: %s, skip", $record->id, $end, $record->begin)); + } + } + }) + ->deselectRecordsAfterCompletion() + ->label(__('admin.resources.exam_user.bulk_action_update_end_label')) + ->icon('heroicon-o-pencil'), ]); } diff --git a/app/Filament/Resources/User/ExamUserResource/Pages/ViewExamUser.php b/app/Filament/Resources/User/ExamUserResource/Pages/ViewExamUser.php index db1060e8..d3efc172 100644 --- a/app/Filament/Resources/User/ExamUserResource/Pages/ViewExamUser.php +++ b/app/Filament/Resources/User/ExamUserResource/Pages/ViewExamUser.php @@ -4,9 +4,11 @@ namespace App\Filament\Resources\User\ExamUserResource\Pages; use App\Filament\Resources\User\ExamUserResource; use App\Repositories\ExamRepository; +use Carbon\Carbon; use Filament\Pages\Actions; use Filament\Resources\Pages\ViewRecord; use Filament\Tables\Table; +use Filament\Forms; class ViewExamUser extends ViewRecord { @@ -16,7 +18,7 @@ class ViewExamUser extends ViewRecord private function getDetailCardData(): array { -// dd($this->record->progressFormatted); +// dd($this->record); $data = []; $record = $this->record; $data[] = [ @@ -82,6 +84,31 @@ class ViewExamUser extends ViewRecord }) ->label(__('admin.resources.exam_user.action_avoid')), + Actions\Action::make('UpdateEnd') + ->mountUsing(fn (Forms\ComponentContainer $form) => $form->fill([ + 'end' => $this->record->end, + ])) + ->form([ + Forms\Components\DateTimePicker::make('end') + ->required() + ->label(__('label.end')) + , + Forms\Components\Textarea::make('reason') + ->label(__('label.reason')) + , + ]) + ->action(function (array $data) { + $examRep = new ExamRepository(); + try { + $examRep->updateExamUserEnd($this->record, Carbon::parse($data['end']), $data['reason'] ?? ""); + $this->notify('success', 'Success !'); + $this->record = $this->resolveRecord($this->record->id); + } catch (\Exception $exception) { + $this->notify('danger', $exception->getMessage()); + } + }) + ->label(__('admin.resources.exam_user.action_update_end')), + Actions\DeleteAction::make(), ]; } diff --git a/app/Models/ExamUser.php b/app/Models/ExamUser.php index d16e4384..8ed336d5 100644 --- a/app/Models/ExamUser.php +++ b/app/Models/ExamUser.php @@ -124,7 +124,7 @@ class ExamUser extends NexusModel return $this->belongsTo(User::class, 'uid'); } - public function progresses() + public function progresses(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(ExamProgress::class, 'exam_user_id'); } diff --git a/app/Repositories/ExamRepository.php b/app/Repositories/ExamRepository.php index 9e9fb3bd..4b7d5d8a 100644 --- a/app/Repositories/ExamRepository.php +++ b/app/Repositories/ExamRepository.php @@ -6,16 +6,14 @@ use App\Models\Exam; use App\Models\ExamProgress; use App\Models\ExamUser; use App\Models\Message; -use App\Models\Setting; use App\Models\Snatch; use App\Models\Torrent; use App\Models\User; use App\Models\UserBanLog; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; @@ -312,11 +310,33 @@ class ExamRepository extends BaseRepository $data = [ 'exam_id' => $exam->id, ]; - if ($begin && $end) { - $logPrefix .= ", specific begin and end"; - $data['begin'] = $begin; - $data['end'] = $end; + if (empty($begin)) { + if (!empty($exam->begin)) { + $begin = $exam->begin; + $logPrefix .= ", begin from exam->begin: $begin"; + } else { + $begin = now(); + $logPrefix .= ", begin from now: $begin"; + } + } else { + $begin = Carbon::parse($begin); } + if (empty($end)) { + if (!empty($exam->end)) { + $end = $exam->end; + $logPrefix .= ", end from exam->end: $end"; + } elseif ($exam->duration > 0) { + $duration = $exam->duration; + $end = $begin->clone()->addDays($duration)->toDateTimeString(); + $logPrefix .= ", end from begin + duration($duration): $end"; + } else { + throw new \RuntimeException("No specific end or duration"); + } + } else { + $end = Carbon::parse($end); + } + $data['begin'] = $begin; + $data['end'] = $end; do_log("$logPrefix, data: " . nexus_json_encode($data)); $examUser = $user->exams()->create($data); $this->updateProgress($examUser, $user); @@ -506,12 +526,19 @@ class ExamRepository extends BaseRepository if (empty($end)) { throw new \InvalidArgumentException("$logPrefix, exam: {$examUser->id} no end."); } + /** + * @var $progressGrouped Collection + */ + $progressGrouped = $examUser->progresses->keyBy("index"); $examUserProgressFieldData = []; $now = now(); foreach ($exam->indexes as $index) { if (!isset($index['checked']) || !$index['checked']) { continue; } + if ($progressGrouped->isNotEmpty() && !$progressGrouped->has($index['index'])) { + continue; + } if (!isset(Exam::$indexes[$index['index']])) { $msg = "Unknown index: {$index['index']}"; do_log("$logPrefix, $msg", 'error'); @@ -721,6 +748,9 @@ class ExamRepository extends BaseRepository if (!isset($index['checked']) || !$index['checked']) { continue; } + if (!isset($progress[$index['index']])) { + continue; + } $currentValue = $progress[$index['index']] ?? 0; $requireValue = $index['require_value']; $unit = Exam::$indexes[$index['index']]['unit'] ?? ''; @@ -769,6 +799,32 @@ class ExamRepository extends BaseRepository return $result; } + public function updateExamUserEnd(ExamUser $examUser, Carbon $end, string $reason = "") + { + if ($end->isBefore($examUser->begin)) { + throw new \InvalidArgumentException(nexus_trans("exam-user.end_can_not_before_begin", ['begin' => $examUser->begin, 'end' => $end])); + } + $oldEndTime = $examUser->end; + $locale = $examUser->user->locale; + $examName = $examUser->exam->name; + Message::add([ + 'sender' => 0, + 'receiver' => $examUser->uid, + 'added' => now(), + 'subject' => nexus_trans('message.exam_user_end_time_updated.subject', [ + 'exam_name' => $examName + ], $locale), + 'msg' => nexus_trans('message.exam_user_end_time_updated.body', [ + 'exam_name' => $examName, + 'old_end_time' => $oldEndTime, + 'new_end_time' => $end, + 'operator' => get_pure_username(), + 'reason' => $reason, + ], $locale), + ]); + $examUser->update(['end' => $end]); + } + public function removeExamUserBulk(array $params, User $user) { $result = $this->getExamUserBulkQuery($params)->delete(); @@ -983,6 +1039,8 @@ class ExamRepository extends BaseRepository $examUser->delete(); continue; } + //update to the newest progress + $examUser = $this->updateProgress($examUser, $examUser->user); $locale = $examUser->user->locale; if ($examUser->is_done) { do_log("$currentLogPrefix, [is_done]"); diff --git a/include/constants.php b/include/constants.php index b2449965..debac99b 100644 --- a/include/constants.php +++ b/include/constants.php @@ -1,6 +1,6 @@ user()->id ?? 0; } +function get_pure_username() +{ + if (IN_NEXUS) { + global $CURUSER; + return $CURUSER["username"] ?? ""; + } + return auth()->user()->username ?? ""; +} + function nexus() { return \Nexus\Nexus::instance(); diff --git a/resources/lang/en/admin.php b/resources/lang/en/admin.php index 23f78647..ab8633dc 100644 --- a/resources/lang/en/admin.php +++ b/resources/lang/en/admin.php @@ -89,7 +89,9 @@ return [ ], 'exam_user' => [ 'bulk_action_avoid_label' => 'Bulk avoid', + 'bulk_action_update_end_label' => 'Bulk modify end time', 'action_avoid' => 'Avoid', + 'action_update_end' => 'Modify end time', 'result_passed' => 'Passed!', 'result_not_passed' => 'Not passed!', ], diff --git a/resources/lang/en/exam-user.php b/resources/lang/en/exam-user.php index 8a9e1e8f..780b90f6 100644 --- a/resources/lang/en/exam-user.php +++ b/resources/lang/en/exam-user.php @@ -11,4 +11,5 @@ return [ \App\Models\ExamUser::STATUS_AVOIDED => 'Avoided', \App\Models\ExamUser::STATUS_NORMAL => 'Normal', ], + 'end_can_not_before_begin' => "End time: :end can't be before begin time: :begin", ]; diff --git a/resources/lang/en/message.php b/resources/lang/en/message.php index a4024309..c9f9abf6 100644 --- a/resources/lang/en/message.php +++ b/resources/lang/en/message.php @@ -43,4 +43,8 @@ BODY, 'subject' => 'Successful torrent purchase reminder', 'body' => 'You spent :bonus bonus to successfully buy the torrent:[url=:url]:torrent_name[/url]', ], + 'exam_user_end_time_updated' => [ + 'subject' => 'Exam :exam_name end time changed', + 'body' => 'The end time of your in-progress exam :exam_name has changed from :old_end_time to :new_end_time. admin: :operator, reason: :reason.', + ], ]; diff --git a/resources/lang/zh_CN/admin.php b/resources/lang/zh_CN/admin.php index 49d21962..2389c818 100644 --- a/resources/lang/zh_CN/admin.php +++ b/resources/lang/zh_CN/admin.php @@ -87,7 +87,9 @@ return [ ], 'exam_user' => [ 'bulk_action_avoid_label' => '批量免除', + 'bulk_action_update_end_label' => '批量修改结束时间', 'action_avoid' => '免除', + 'action_update_end' => '修改结束时间', 'result_passed' => '通过!', 'result_not_passed' => '未通过!', ], diff --git a/resources/lang/zh_CN/exam-user.php b/resources/lang/zh_CN/exam-user.php index 2bb61e82..00bf1977 100644 --- a/resources/lang/zh_CN/exam-user.php +++ b/resources/lang/zh_CN/exam-user.php @@ -11,4 +11,5 @@ return [ \App\Models\ExamUser::STATUS_AVOIDED => '已免除', \App\Models\ExamUser::STATUS_NORMAL => '考核中', ], + 'end_can_not_before_begin' => '结束时间::end 不能在开始时间::begin 之前', ]; diff --git a/resources/lang/zh_CN/message.php b/resources/lang/zh_CN/message.php index 0f304240..4e195039 100644 --- a/resources/lang/zh_CN/message.php +++ b/resources/lang/zh_CN/message.php @@ -43,4 +43,8 @@ BODY, 'subject' => '成功购买种子提醒', 'body' => '你花费 :bonus 魔力成功购买了种子:[url=:url]:torrent_name[/url]', ], + 'exam_user_end_time_updated' => [ + 'subject' => '考核 :exam_name 结束时间变更', + 'body' => '你进行中的考核::exam_name 的结束时间由 :old_end_time 变更为 :new_end_time。管理员::operator,原因::reason。', + ], ]; diff --git a/resources/lang/zh_TW/admin.php b/resources/lang/zh_TW/admin.php index c6ffe422..18b7d534 100644 --- a/resources/lang/zh_TW/admin.php +++ b/resources/lang/zh_TW/admin.php @@ -89,7 +89,9 @@ return [ ], 'exam_user' => [ 'bulk_action_avoid_label' => '批量免除', + 'bulk_action_update_end_label' => '批量修改結束時間', 'action_avoid' => '免除', + 'action_update_end' => '修改結束時間', 'result_passed' => '通過!', 'result_not_passed' => '未通過!', ], diff --git a/resources/lang/zh_TW/exam-user.php b/resources/lang/zh_TW/exam-user.php index b6fb8a19..f70a8f32 100644 --- a/resources/lang/zh_TW/exam-user.php +++ b/resources/lang/zh_TW/exam-user.php @@ -11,4 +11,5 @@ return [ \App\Models\ExamUser::STATUS_AVOIDED => '已免除', \App\Models\ExamUser::STATUS_NORMAL => '考核中', ], + 'end_can_not_before_begin' => '結束時間::end 不能在開始時間::begin 之前', ]; diff --git a/resources/lang/zh_TW/message.php b/resources/lang/zh_TW/message.php index 52d0ffff..b1035fc9 100644 --- a/resources/lang/zh_TW/message.php +++ b/resources/lang/zh_TW/message.php @@ -42,4 +42,8 @@ BODY, 'subject' => '成功購買種子提醒', 'body' => '你花費 :bonus 魔力成功購買了種子:[url=:url]:torrent_name[/url]', ], + 'exam_user_end_time_updated' => [ + 'subject' => '考核 :exam_name 結束時間變更', + 'body' => '你進行中的考核::exam_name 的結束時間由 :old_end_time 變更為 :new_end_time。管理員::operator,原因::reason。', + ], ];