diff --git a/app/Admin/Controllers/Hr/HolidayController.php b/app/Admin/Controllers/Hr/HolidayController.php new file mode 100644 index 0000000..955a014 --- /dev/null +++ b/app/Admin/Controllers/Hr/HolidayController.php @@ -0,0 +1,158 @@ +baseCRUD() + ->tableLayout('fixed') + ->headerToolbar([ + $this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.holiday.create')), + ...$this->baseHeaderToolBar(), + ]) + ->bulkActions([]) + ->filter($this->baseFilter()->body([ + amis()->GroupControl()->mode('horizontal')->body([ + amisMake()->SelectControl()->name('store_id')->label(__('holiday_apply.store_id')) + ->source(admin_url('api/stores?_all=1')) + ->labelField('title') + ->valueField('id') + ->searchable() + ->columnRatio(3) + ->clearable(), + amisMake()->TextControl()->name('employee_name')->label(__('holiday_apply.employee_id')) + ->placeholder(__('employee.name').'/'.__('employee.phone')) + ->columnRatio(3) + ->clearable(), + amisMake()->SelectControl()->name('type_id')->label(__('holiday_apply.type_id')) + ->source(admin_url('api/keywords/tree-list?parent_key=holiday_type')) + ->labelField('name') + ->valueField('key') + ->columnRatio(3) + ->clearable(), + amisMake()->SelectControl()->name('check_status')->label(__('holiday_apply.check_status')) + ->options(CheckStatus::options()) + ->columnRatio(3) + ->clearable(), + ]), + ])) + ->columns([ + amisMake()->TableColumn()->name('store.title')->label(__('employee_sign.store_id')), + amisMake()->TableColumn()->name('employee.name')->label(__('holiday_apply.employee_id')), + amisMake()->TableColumn()->name('type.name')->label(__('holiday_apply.type_id')), + amisMake()->TableColumn()->name('reason')->label(__('holiday_apply.reason')), + amisMake()->TableColumn()->name('start_at')->label(__('holiday_apply.start_at')), + amisMake()->TableColumn()->name('end_at')->label(__('holiday_apply.end_at')), + amisMake()->TableColumn()->name('check_status')->label(__('holiday_apply.check_status'))->set('type', 'mapping')->map(CheckStatus::options()), + amisMake()->TableColumn()->name('created_at')->label(__('holiday_apply.created_at')), + $this->rowActions([ + $this->rowShowButton()->visible(Admin::user()->can('admin.hr.holiday.view')), + $this->rowEditTypeButton('drawer', 'xl') + ->visible(Admin::user()->can('admin.hr.holiday.update')) + ->visibleOn('${OR(check_status == '.CheckStatus::None->value.', check_status == '.CheckStatus::Cancel->value.', check_status == '.CheckStatus::Fail->value.')}'), + $this->rowDeleteButton() + ->visible(Admin::user()->can('admin.hr.holiday.delete')) + ->visibleOn('${OR(check_status == '.CheckStatus::None->value.', check_status == '.CheckStatus::Cancel->value.', check_status == '.CheckStatus::Fail->value.')}'), + $this->applyAction(), + $this->cancelAction(), + ]), + ]); + + return $this->baseList($crud); + } + + public function form($edit): Form + { + return $this->baseForm()->title('')->body([ + amisMake()->SelectControl()->name('employee_id')->label(__('holiday_apply.employee_id')) + ->source(admin_url('api/employees?_all=1&store_id_gt=0&employee_status='.EmployeeStatus::Online->value)) + ->labelField('name') + ->valueField('id') + ->searchable() + ->joinValues(false) + ->extractValue() + ->required(), + amisMake()->SelectControl() + ->name('type_id') + ->label(__('holiday_apply.type_id')) + ->source(admin_url('api/keywords/tree-list?parent_key=holiday_type')) + ->labelField('name') + ->valueField('key') + ->required(), + amisMake()->DateRangeControl() + ->format('YYYY-MM-DD') + ->name('date_range') + ->label(__('holiday_apply.date')) + ->required(), + amisMake()->TextControl()->name('reason')->label(__('holiday_apply.reason'))->required(), + ]); + } + + public function detail(): Form + { + $subjectType = $this->getMorphAlias(); + $detail = amisMake()->Property()->items([ + ['label' => __('holiday_apply.store_id'), 'content' => '${store.title}'], + ['label' => __('holiday_apply.employee_id'), 'content' => '${employee.name}'], + ['label' => __('holiday_apply.type_id'), 'content' => '${type.name}'], + ['label' => __('holiday_apply.date'), 'content' => '${start_at} ${end_at}'], + ['label' => __('holiday_apply.reason'), 'content' => '${reason}'], + ['label' => __('holiday_apply.created_at'), 'content' => '${created_at}'], + ['label' => __('holiday_apply.check_status'), 'content' => amisMake()->Mapping()->name('check_status')->map(CheckStatus::options())], + ['label' => __('holiday_apply.checked_at'), 'content' => '${checked_at}'], + ['label' => __('holiday_apply.check_remarks'), 'content' => '${check_remarks}'], + ]); + $table = amisMake()->Service() + ->id('holiday-checklog-table') + ->initFetch(false) + ->api( + amisMake()->BaseApi()->method('get')->url(admin_url('api/workflow/logs'))->data([ + 'subject_type' => $subjectType, + 'subject_id' => '${id}', + ]) + ) + ->body( + amisMake()->Table()->columnsTogglable(false)->itemActions([ + $this->succesAction()->reload('holiday-detail'), + $this->failAction()->reload('holiday-detail'), + ])->columns([ + amisMake()->TableColumn()->name('batch_id')->label(__('workflow_log.batch_id')), + amisMake()->TableColumn()->name('check_name')->label(__('workflow_log.check_name')), + amisMake()->TableColumn()->name('check_user.name')->label(__('workflow_log.check_user_id')), + amisMake()->TableColumn()->name('check_status')->label(__('workflow_log.check_status'))->set('type', 'mapping')->map(CheckStatus::options()), + amisMake()->TableColumn()->name('checked_at')->label(__('workflow_log.checked_at')), + amisMake()->TableColumn()->name('remarks')->label(__('workflow_log.remarks')), + ]) + ); + return $this->baseDetail()->id('holiday-detail')->title('')->onEvent([ + 'inited' => [ + 'actions' => [ + ['actionType' => 'reload', 'componentId' => 'holiday-checklog-table'], + ] + ] + ])->body([$detail, amisMake()->Divider(), $table]); + } + + public function getMorphAlias() + { + return (new HolidayApply)->getMorphClass(); + } +} \ No newline at end of file diff --git a/app/Admin/Filters/EmployeeSignRepairFilter.php b/app/Admin/Filters/EmployeeSignRepairFilter.php index 4b467ff..6eedcf1 100644 --- a/app/Admin/Filters/EmployeeSignRepairFilter.php +++ b/app/Admin/Filters/EmployeeSignRepairFilter.php @@ -32,4 +32,9 @@ class EmployeeSignRepairFilter extends ModelFilter $end = Carbon::createFromTimestamp(data_get($dates, 1, time()))->endOfDay(); $this->whereBetween('date', [$start, $end]); } + + public function checkStatus($key) + { + $this->where('check_status', $key); + } } diff --git a/app/Admin/Filters/HolidayApplyFilter.php b/app/Admin/Filters/HolidayApplyFilter.php new file mode 100644 index 0000000..ea9ffcf --- /dev/null +++ b/app/Admin/Filters/HolidayApplyFilter.php @@ -0,0 +1,36 @@ + [ + 'store_title' => 'title', + ], + 'employee' => [ + 'employee_name' => 'name', + 'employee_search' => 'search', + ], + ]; + + public function employeeId($key) + { + $this->where('employee_id', $key); + } + + public function typeId($key) + { + $this->where('type_id', $key); + } + + public function checkStatus($key) + { + $this->where('check_status', $key); + } +} diff --git a/app/Admin/Services/HolidayApplyService.php b/app/Admin/Services/HolidayApplyService.php new file mode 100644 index 0000000..c315ff8 --- /dev/null +++ b/app/Admin/Services/HolidayApplyService.php @@ -0,0 +1,55 @@ +value('store_id'); + } + // 处理日期 + if (isset($data['date_range']) && !isset($data['start_at']) && !isset($data['end_at'])) { + $dates = explode(',', $data['date_range']); + $data['start_at'] = Carbon::createFromFormat('Y-m-d', data_get($dates, 0))->startOfDay(); + $data['end_at'] = Carbon::createFromFormat('Y-m-d', data_get($dates, 1))->endOfDay(); + } + return $data; + } + + public function validate($data, $model = null) + { + // 验证申请时间是否重叠 + if (HolidayApply::where('employee_id', data_get($data, 'employee_id', $model?->employee_id))->where(fn($q) => $q->whereBetween('start_at', [$data['start_at'], $data['end_at']])->orWhereBetween('end_at', [$data['start_at'], $data['end_at']]))->exists()) { + return '该时间段已经申请过了'; + } + $createRules = [ + 'employee_id' => ['required'], + 'type_id' => ['required'], + 'reason' => ['required'], + 'start_at' => ['required'], + 'end_at' => ['required'], + ]; + $updateRules = []; + $validator = Validator::make($data, $model ? $updateRules : $createRules, []); + if ($validator->fails()) { + return $validator->errors()->first(); + } + return true; + } +} \ No newline at end of file diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 6c9a9c1..cc41341 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -11,6 +11,7 @@ use App\Admin\Controllers\Hr\RestController; use App\Admin\Controllers\Hr\SignController; use App\Admin\Controllers\Hr\SignLogController; use App\Admin\Controllers\Hr\SignRepairController; +use App\Admin\Controllers\Hr\HolidayController; use App\Admin\Controllers\Store\DeviceController; use App\Admin\Controllers\Store\EmployeeController as StoreEmployeeController; use App\Admin\Controllers\Store\StoreController; @@ -81,6 +82,8 @@ Route::group([ $router->resource('signs', SignController::class)->only(['index', 'show']); // 补卡申请 $router->resource('repairs', SignRepairController::class); + // 请假申请 + $router->resource('holiday', HolidayController::class); }); /* diff --git a/app/Models/EmployeeSignRepair.php b/app/Models/EmployeeSignRepair.php index 9056fe9..c0808f6 100644 --- a/app/Models/EmployeeSignRepair.php +++ b/app/Models/EmployeeSignRepair.php @@ -3,7 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -use App\Enums\SignTime; +use App\Enums\{SignTime, CheckStatus}; use App\Traits\HasDateTimeFormatter; use EloquentFilter\Filterable; use App\Contracts\Checkable; @@ -23,7 +23,8 @@ class EmployeeSignRepair extends Model implements Checkable protected $casts = [ 'date' => 'date:Y-m-d', 'checked_at' => 'datetime', - 'repair_type' => SignTime::class + 'repair_type' => SignTime::class, + 'check_status' => CheckStatus::class, ]; public function modelFilter() diff --git a/app/Models/HolidayApply.php b/app/Models/HolidayApply.php new file mode 100644 index 0000000..f744f0d --- /dev/null +++ b/app/Models/HolidayApply.php @@ -0,0 +1,42 @@ + 'date:Y-m-d', + 'end_at' => 'date:Y-m-d', + 'check_status' => CheckStatus::class, + ]; + + public function modelFilter() + { + return App\Admin\Filters\HolidayApplyFilter::class; + } + + public function store() + { + return $this->belongsTo(Store::class, 'store_id'); + } + + public function type() + { + // holiday_type + return $this->belongsTo(Keyword::class, 'type_id', 'key'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 6d2441d..770b50b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -32,6 +32,7 @@ class AppServiceProvider extends ServiceProvider collect([ \App\Models\AdminUser::class, \App\Models\EmployeeSignRepair::class, + \App\Models\HolidayApply::class, ])->mapWithKeys(fn ($model) => [(new $model)->getTable() => $model])->all() ); } diff --git a/database/migrations/2024_04_02_093734_create_holiday_apply_table.php b/database/migrations/2024_04_02_093734_create_holiday_apply_table.php new file mode 100644 index 0000000..f7222c9 --- /dev/null +++ b/database/migrations/2024_04_02_093734_create_holiday_apply_table.php @@ -0,0 +1,39 @@ +id(); + $table->foreignId('store_id')->comment('门店, stores.id'); + $table->foreignId('employee_id')->comment('申请人, employees.id'); + $table->datetime('start_at')->comment('结束时间'); + $table->datetime('end_at')->comment('开始时间'); + $table->string('type_id')->comment('类型(holiday_type), keywords.key'); + $table->string('reason')->comment('事由'); + + $table->unsignedInteger('check_status')->default(CheckStatus::None->value)->comment('审核状态'); + $table->timestamp('checked_at')->nullable()->comment('审核通过时间'); + $table->string('check_remarks')->nullable()->comment('审核未通过原因'); + $table->timestamps(); + $table->comment('请假申请'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('holiday_applies'); + } +}; diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php index 50eadd8..c9df502 100644 --- a/database/seeders/AdminPermissionSeeder.php +++ b/database/seeders/AdminPermissionSeeder.php @@ -128,6 +128,12 @@ class AdminPermissionSeeder extends Seeder 'uri' => '/hr/repairs', 'resource' => true, ], + 'holiday' => [ + 'name' => '请假申请', + 'icon' => '', + 'uri' => '/hr/holiday', + 'resource' => true, + ] ], ], diff --git a/database/seeders/KeywordSeeder.php b/database/seeders/KeywordSeeder.php index a9c0508..33004bb 100644 --- a/database/seeders/KeywordSeeder.php +++ b/database/seeders/KeywordSeeder.php @@ -84,6 +84,11 @@ class KeywordSeeder extends Seeder 'name' => '上报数据类型', 'children' => ['大乐透', '七星彩', '排列三', '排列五', '合买', '即开', ['key' => 'ledger_item_type_other', 'name' => '其他']], ], + [ + 'key' => 'holiday_type', + 'name' => '请假类型', + 'children' => ['病假', '事假'], + ] ]; $this->insertKeywors($keywords); diff --git a/database/seeders/WorkflowSeeder.php b/database/seeders/WorkflowSeeder.php index 9148524..f5efd33 100644 --- a/database/seeders/WorkflowSeeder.php +++ b/database/seeders/WorkflowSeeder.php @@ -13,12 +13,13 @@ class WorkflowSeeder extends Seeder public function run(): void { $now = now(); - $config = [ + $config = json_encode([ ["sort" => 1, "type" => "user", "user" => 1, "title" => "员工", "value" => 1, "subTitle" => "Admin"], - ]; + ]); Workflow::truncate(); Workflow::insert([ - ['key' => 'employee_sign_repair', 'name' => '补卡申请', 'config' => json_encode($config), 'created_at' => now(), 'updated_at' => now()] + ['key' => 'employee_sign_repair', 'name' => '补卡申请', 'config' => $config, 'created_at' => now(), 'updated_at' => now()], + ['key' => 'holiday_apply', 'name' => '请假申请', 'config' => $config, 'created_at' => now(), 'updated_at' => now()], ]); } } diff --git a/lang/zh_CN/holiday_apply.php b/lang/zh_CN/holiday_apply.php new file mode 100644 index 0000000..684e7cf --- /dev/null +++ b/lang/zh_CN/holiday_apply.php @@ -0,0 +1,19 @@ + 'ID', + 'created_at' => '提交日期', + 'updated_at' => '更新时间', + + 'check_status' => '审核状态', + 'checked_at' => '审核通过时间', + 'check_remarks' => '审核未通过原因', + 'start_at' => '开始时间', + 'end_at' => '结束时间', + 'date' => '请假日期', + + 'store_id' => '门店', + 'employee_id' => '员工', + 'reason' => '请假事由', + 'type_id' => '请假类型', +];