mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-29 08:27:23 +08:00
exam add recurring
This commit is contained in:
@@ -91,6 +91,12 @@ class ExamResource extends Resource
|
|||||||
->columnSpan(['sm' => 2])
|
->columnSpan(['sm' => 2])
|
||||||
->label(__('label.duration'))
|
->label(__('label.duration'))
|
||||||
->helperText(__('label.exam.duration_help')),
|
->helperText(__('label.exam.duration_help')),
|
||||||
|
Forms\Components\Select::make('recurring')
|
||||||
|
->options(Exam::listRecurringOptions())
|
||||||
|
->label(__('exam.recurring'))
|
||||||
|
->helperText(__('exam.recurring_help'))
|
||||||
|
->columnSpan(['sm' => 2])
|
||||||
|
,
|
||||||
])->columns(2),
|
])->columns(2),
|
||||||
|
|
||||||
Forms\Components\Section::make(__('label.exam.section_target_user'))->schema([
|
Forms\Components\Section::make(__('label.exam.section_target_user'))->schema([
|
||||||
@@ -122,6 +128,7 @@ class ExamResource extends Resource
|
|||||||
Tables\Columns\TextColumn::make('begin')->label(__('label.begin')),
|
Tables\Columns\TextColumn::make('begin')->label(__('label.begin')),
|
||||||
Tables\Columns\TextColumn::make('end')->label(__('label.end')),
|
Tables\Columns\TextColumn::make('end')->label(__('label.end')),
|
||||||
Tables\Columns\TextColumn::make('durationText')->label(__('label.duration')),
|
Tables\Columns\TextColumn::make('durationText')->label(__('label.duration')),
|
||||||
|
Tables\Columns\TextColumn::make('recurringText')->label(__('exam.recurring')),
|
||||||
Tables\Columns\TextColumn::make('filterFormatted')->label(__('label.exam.filter_formatted'))->html(),
|
Tables\Columns\TextColumn::make('filterFormatted')->label(__('label.exam.filter_formatted'))->html(),
|
||||||
Tables\Columns\BooleanColumn::make('is_discovered')->label(__('label.exam.is_discovered')),
|
Tables\Columns\BooleanColumn::make('is_discovered')->label(__('label.exam.is_discovered')),
|
||||||
Tables\Columns\TextColumn::make('priority')->label(__('label.priority')),
|
Tables\Columns\TextColumn::make('priority')->label(__('label.priority')),
|
||||||
|
|||||||
+61
-19
@@ -9,7 +9,10 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class Exam extends NexusModel
|
class Exam extends NexusModel
|
||||||
{
|
{
|
||||||
protected $fillable = ['name', 'description', 'begin', 'end', 'duration', 'status', 'is_discovered', 'filters', 'indexes', 'priority'];
|
protected $fillable = [
|
||||||
|
'name', 'description', 'begin', 'end', 'duration', 'status', 'is_discovered', 'filters', 'indexes', 'priority',
|
||||||
|
'recurring',
|
||||||
|
];
|
||||||
|
|
||||||
public $timestamps = true;
|
public $timestamps = true;
|
||||||
|
|
||||||
@@ -62,6 +65,9 @@ class Exam extends NexusModel
|
|||||||
self::FILTER_USER_REGISTER_DAYS_RANGE => ['name' => 'User register days range'],
|
self::FILTER_USER_REGISTER_DAYS_RANGE => ['name' => 'User register days range'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const RECURRING_WEEKLY = "Weekly";
|
||||||
|
const RECURRING_MONTHLY = "Monthly";
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::saving(function (Model $model) {
|
static::saving(function (Model $model) {
|
||||||
@@ -84,6 +90,19 @@ class Exam extends NexusModel
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function listRecurringOptions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::RECURRING_WEEKLY => nexus_trans("exam.recurring_weekly"),
|
||||||
|
self::RECURRING_MONTHLY => nexus_trans("exam.recurring_monthly"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
protected function getRecurringTextAttribute(): string
|
||||||
|
{
|
||||||
|
$options = self::listRecurringOptions();
|
||||||
|
return $options[$this->recurring] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
public function getStatusTextAttribute(): string
|
public function getStatusTextAttribute(): string
|
||||||
{
|
{
|
||||||
return $this->status == self::STATUS_ENABLED ? nexus_trans('label.enabled') : nexus_trans('label.disabled');
|
return $this->status == self::STATUS_ENABLED ? nexus_trans('label.enabled') : nexus_trans('label.disabled');
|
||||||
@@ -164,28 +183,51 @@ class Exam extends NexusModel
|
|||||||
return implode("<br/>", $arr);
|
return implode("<br/>", $arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function beginForUser(): Attribute
|
public function getBeginForUser(): Carbon
|
||||||
{
|
{
|
||||||
return new Attribute(
|
if (!empty($this->begin)) {
|
||||||
get: fn ($value) => $value ? Carbon::parse($value) : Carbon::now()
|
return Carbon::parse($this->begin);
|
||||||
);
|
}
|
||||||
|
if (!empty($this->recurring)) {
|
||||||
|
return $this->getRecurringBegin(Carbon::now());
|
||||||
|
}
|
||||||
|
return Carbon::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function endForUser(): Attribute
|
public function getEndForUser(): Carbon
|
||||||
{
|
{
|
||||||
return new Attribute(
|
if (!empty($this->end)) {
|
||||||
get: function ($value, $attributes) {
|
return Carbon::parse($this->end);
|
||||||
if ($value) {
|
}
|
||||||
return Carbon::parse($value);
|
if (!empty($this->duration)) {
|
||||||
}
|
return $this->getBeginForUser()->clone()->addDays($this->duration);
|
||||||
if (!empty($attributes['duration'])) {
|
}
|
||||||
/** @var Carbon $begin */
|
if (!empty($this->recurring)) {
|
||||||
$begin = $this->begin_for_user;
|
return $this->getRecurringEnd(Carbon::now());
|
||||||
return $begin->clone()->addDays($attributes['duration']);
|
}
|
||||||
}
|
throw new \RuntimeException(nexus_trans("exam.time_condition_invalid"));
|
||||||
throw new \RuntimeException("No specific end or duration");
|
}
|
||||||
}
|
|
||||||
);
|
public function getRecurringBegin(Carbon $time): Carbon
|
||||||
|
{
|
||||||
|
$recurring = $this->recurring;
|
||||||
|
if ($recurring == self::RECURRING_WEEKLY) {
|
||||||
|
return $time->startOfWeek();
|
||||||
|
} elseif ($recurring == self::RECURRING_MONTHLY) {
|
||||||
|
return $time->startOfMonth();
|
||||||
|
}
|
||||||
|
throw new \RuntimeException("Invalid recurring: $recurring");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRecurringEnd(Carbon $time): Carbon
|
||||||
|
{
|
||||||
|
$recurring = $this->recurring;
|
||||||
|
if ($recurring == self::RECURRING_WEEKLY) {
|
||||||
|
return $time->endOfWeek();
|
||||||
|
} elseif ($recurring == self::RECURRING_MONTHLY) {
|
||||||
|
return $time->endOfMonth();
|
||||||
|
}
|
||||||
|
throw new \RuntimeException("Invalid recurring: $recurring");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,14 +101,29 @@ class ExamRepository extends BaseRepository
|
|||||||
|
|
||||||
private function checkBeginEnd(array $params): bool
|
private function checkBeginEnd(array $params): bool
|
||||||
{
|
{
|
||||||
if (!empty($params['begin']) && !empty($params['end']) && empty($params['duration'])) {
|
if (
|
||||||
|
!empty($params['begin']) && !empty($params['end'])
|
||||||
|
&& empty($params['duration'])
|
||||||
|
&& empty($params['recurring'])
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (empty($params['begin']) && empty($params['end']) && isset($params['duration']) && ctype_digit((string)$params['duration']) && $params['duration'] > 0) {
|
if (
|
||||||
|
empty($params['begin']) && empty($params['end'])
|
||||||
|
&& isset($params['duration']) && ctype_digit((string)$params['duration']) && $params['duration'] > 0
|
||||||
|
&& empty($params['recurring'])
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
empty($params['begin']) && empty($params['end'])
|
||||||
|
&& empty($params['duration'])
|
||||||
|
&& !empty($params['recurring'])
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \InvalidArgumentException("Require begin and end or only duration.");
|
throw new \InvalidArgumentException(nexus_trans("exam.time_condition_invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkFilters(array $params)
|
private function checkFilters(array $params)
|
||||||
@@ -231,7 +246,7 @@ class ExamRepository extends BaseRepository
|
|||||||
$now = Carbon::now();
|
$now = Carbon::now();
|
||||||
$query = Exam::query()
|
$query = Exam::query()
|
||||||
->where('status', Exam::STATUS_ENABLED)
|
->where('status', Exam::STATUS_ENABLED)
|
||||||
->whereRaw("if(begin is not null and end is not null, begin <= '$now' and end >= '$now', duration > 0)")
|
->whereRaw("if(begin is not null and end is not null, begin <= '$now' and end >= '$now', duration > 0 or recurring is not null)")
|
||||||
;
|
;
|
||||||
|
|
||||||
if (!is_null($excludeId)) {
|
if (!is_null($excludeId)) {
|
||||||
@@ -330,6 +345,7 @@ class ExamRepository extends BaseRepository
|
|||||||
public function assignToUser(int $uid, int $examId, $begin = null, $end = null)
|
public function assignToUser(int $uid, int $examId, $begin = null, $end = null)
|
||||||
{
|
{
|
||||||
$logPrefix = "uid: $uid, examId: $examId, begin: $begin, end: $end";
|
$logPrefix = "uid: $uid, examId: $examId, begin: $begin, end: $end";
|
||||||
|
/** @var Exam $exam */
|
||||||
$exam = Exam::query()->find($examId);
|
$exam = Exam::query()->find($examId);
|
||||||
$user = User::query()->findOrFail($uid);
|
$user = User::query()->findOrFail($uid);
|
||||||
if (Auth::user()->class <= $user->class) {
|
if (Auth::user()->class <= $user->class) {
|
||||||
@@ -349,12 +365,12 @@ class ExamRepository extends BaseRepository
|
|||||||
'exam_id' => $exam->id,
|
'exam_id' => $exam->id,
|
||||||
];
|
];
|
||||||
if (empty($begin)) {
|
if (empty($begin)) {
|
||||||
$begin = $exam->begin_for_user;
|
$begin = $exam->getBeginForUser();
|
||||||
} else {
|
} else {
|
||||||
$begin = Carbon::parse($begin);
|
$begin = Carbon::parse($begin);
|
||||||
}
|
}
|
||||||
if (empty($end)) {
|
if (empty($end)) {
|
||||||
$end = $exam->end_for_user;
|
$end = $exam->getEndForUser();
|
||||||
} else {
|
} else {
|
||||||
$end = Carbon::parse($end);
|
$end = Carbon::parse($end);
|
||||||
}
|
}
|
||||||
@@ -1003,8 +1019,8 @@ class ExamRepository extends BaseRepository
|
|||||||
$size = 1000;
|
$size = 1000;
|
||||||
$minId = 0;
|
$minId = 0;
|
||||||
$result = 0;
|
$result = 0;
|
||||||
$begin = $exam->begin_for_user;
|
$begin = $exam->getBeginForUser();
|
||||||
$end = $exam->end_for_user;
|
$end = $exam->getEndForUser();
|
||||||
while (true) {
|
while (true) {
|
||||||
$logPrefix = sprintf('[%s], exam: %s, size: %s', __FUNCTION__, $exam->id , $size);
|
$logPrefix = sprintf('[%s], exam: %s, size: %s', __FUNCTION__, $exam->id , $size);
|
||||||
$users = (clone $baseQuery)->where("$userTable.id", ">", $minId)->limit($size)->get();
|
$users = (clone $baseQuery)->where("$userTable.id", ">", $minId)->limit($size)->get();
|
||||||
@@ -1069,10 +1085,12 @@ class ExamRepository extends BaseRepository
|
|||||||
$result += $examUsers->count();
|
$result += $examUsers->count();
|
||||||
$now = Carbon::now()->toDateTimeString();
|
$now = Carbon::now()->toDateTimeString();
|
||||||
$examUserIdArr = $uidToDisable = $messageToSend = $userBanLog = $userModcommentUpdate = [];
|
$examUserIdArr = $uidToDisable = $messageToSend = $userBanLog = $userModcommentUpdate = [];
|
||||||
|
$examUserToInsert = [];
|
||||||
foreach ($examUsers as $examUser) {
|
foreach ($examUsers as $examUser) {
|
||||||
$minId = $examUser->id;
|
$minId = $examUser->id;
|
||||||
$examUserIdArr[] = $examUser->id;
|
$examUserIdArr[] = $examUser->id;
|
||||||
$uid = $examUser->uid;
|
$uid = $examUser->uid;
|
||||||
|
/** @var Exam $exam */
|
||||||
$exam = $examUser->exam;
|
$exam = $examUser->exam;
|
||||||
$currentLogPrefix = sprintf("$logPrefix, user: %s, exam: %s, examUser: %s", $uid, $examUser->exam_id, $examUser->id);
|
$currentLogPrefix = sprintf("$logPrefix, user: %s, exam: %s, examUser: %s", $uid, $examUser->exam_id, $examUser->id);
|
||||||
if (!$examUser->user) {
|
if (!$examUser->user) {
|
||||||
@@ -1124,8 +1142,18 @@ class ExamRepository extends BaseRepository
|
|||||||
'subject' => $subject,
|
'subject' => $subject,
|
||||||
'msg' => $msg
|
'msg' => $msg
|
||||||
];
|
];
|
||||||
|
if (!empty($exam->recurring) && $this->isExamMatchUser($exam, $examUser->user)) {
|
||||||
|
$examUserToInsert[] = [
|
||||||
|
'uid' => $examUser->user->id,
|
||||||
|
'exam_id' => $exam->id,
|
||||||
|
'begin' => $exam->getBeginForUser(),
|
||||||
|
'end' => $exam->getEndForUser(),
|
||||||
|
'created_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DB::transaction(function () use ($uidToDisable, $messageToSend, $examUserIdArr, $userBanLog, $userModcommentUpdate, $userTable, $logPrefix) {
|
DB::transaction(function () use ($uidToDisable, $messageToSend, $examUserIdArr, $examUserToInsert, $userBanLog, $userModcommentUpdate, $userTable, $logPrefix) {
|
||||||
ExamUser::query()->whereIn('id', $examUserIdArr)->update(['status' => ExamUser::STATUS_FINISHED]);
|
ExamUser::query()->whereIn('id', $examUserIdArr)->update(['status' => ExamUser::STATUS_FINISHED]);
|
||||||
do {
|
do {
|
||||||
$deleted = ExamProgress::query()->whereIn('exam_user_id', $examUserIdArr)->limit(10000)->delete();
|
$deleted = ExamProgress::query()->whereIn('exam_user_id', $examUserIdArr)->limit(10000)->delete();
|
||||||
@@ -1144,6 +1172,9 @@ class ExamRepository extends BaseRepository
|
|||||||
if (!empty($userBanLog)) {
|
if (!empty($userBanLog)) {
|
||||||
UserBanLog::query()->insert($userBanLog);
|
UserBanLog::query()->insert($userBanLog);
|
||||||
}
|
}
|
||||||
|
if (!empty($examUserToInsert)) {
|
||||||
|
ExamUser::query()->insert($examUserToInsert);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('exams', function (Blueprint $table) {
|
||||||
|
$table->string("recurring")->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('exams', function (Blueprint $table) {
|
||||||
|
$table->dropColumn("recurring");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -32,5 +32,11 @@ return [
|
|||||||
'list' => [
|
'list' => [
|
||||||
'page_title' => '考核列表'
|
'page_title' => '考核列表'
|
||||||
]
|
]
|
||||||
]
|
],
|
||||||
|
'recurring' => '周期性',
|
||||||
|
'recurring_weekly' => '每周一次',
|
||||||
|
'recurring_monthly' => '每月一次',
|
||||||
|
'recurring_help' => '如果指定为周期性,考核开始时间为当前周期的开始时间,结束时间为当前周期的结束时间,这里说的都是自然周/月。每个周期结束后,如果用户仍然满足筛选条件,会自动为用户分配下个周期的任务。',
|
||||||
|
|
||||||
|
'time_condition_invalid' => '时间参数不合理,有且只有三项之一:开始时间+结束时间/时长/周期性',
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user