diff --git a/app/Admin/Controllers/Hr/OfficalBusinessController.php b/app/Admin/Controllers/Hr/OfficalBusinessController.php new file mode 100644 index 0000000..9e0e727 --- /dev/null +++ b/app/Admin/Controllers/Hr/OfficalBusinessController.php @@ -0,0 +1,145 @@ +baseCRUD() + ->tableLayout('fixed') + ->headerToolbar([ + $this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.business.create')), + ...$this->baseHeaderToolBar(), + ]) + ->bulkActions([]) + ->filter($this->baseFilter()->body([ + amis()->GroupControl()->mode('horizontal')->body([ + amisMake()->SelectControl()->name('store_id')->label(__('offical_business.store_id')) + ->source(admin_url('api/stores?_all=1')) + ->labelField('title') + ->valueField('id') + ->searchable() + ->columnRatio(3) + ->clearable(), + amisMake()->TextControl()->name('employee_name')->label(__('offical_business.employee_id')) + ->placeholder(__('employee.name').'/'.__('employee.phone')) + ->columnRatio(3) + ->clearable(), + amisMake()->SelectControl()->name('check_status')->label(__('offical_business.check_status')) + ->options(CheckStatus::options()) + ->columnRatio(3) + ->clearable(), + ]), + ])) + ->columns([ + amisMake()->TableColumn()->name('store.title')->label(__('offical_business.store_id')), + amisMake()->TableColumn()->name('employee.name')->label(__('offical_business.employee_id')), + amisMake()->TableColumn()->name('start_at')->label(__('offical_business.date')), + amisMake()->TableColumn()->name('end_at')->label(__('offical_business.date')), + amisMake()->TableColumn()->name('address')->label(__('offical_business.address')), + amisMake()->TableColumn()->name('reason')->label(__('offical_business.reason')), + amisMake()->TableColumn()->name('check_status')->label(__('offical_business.check_status'))->set('type', 'mapping')->map(CheckStatus::options()), + amisMake()->TableColumn()->name('created_at')->label(__('offical_business.created_at')), + $this->rowActions([ + $this->rowShowButton()->visible(Admin::user()->can('admin.hr.business.view')), + $this->rowEditTypeButton('drawer', 'xl') + ->visible(Admin::user()->can('admin.hr.business.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.business.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(__('offical_business.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()->DateRangeControl() + ->name('date_range') + ->label(__('offical_business.date')) + ->required(), + amisMake()->TextControl()->name('address')->label(__('offical_business.address'))->required(), + amisMake()->TextControl()->name('reason')->label(__('offical_business.reason')), + ]); + } + + public function detail(): Form + { + $subjectType = $this->getMorphAlias(); + $detail = amisMake()->Property()->items([ + ['label' => __('offical_business.store_id'), 'content' => '${store.title}'], + ['label' => __('offical_business.employee_id'), 'content' => '${employee.name}'], + ['label' => __('offical_business.date'), 'content' => '${start_at} ${end_at}'], + ['label' => __('offical_business.address'), 'content' => '${address}'], + ['label' => __('offical_business.reason'), 'content' => '${reason}'], + ['label' => __('offical_business.created_at'), 'content' => '${created_at}'], + ['label' => __('offical_business.check_status'), 'content' => amisMake()->Mapping()->name('check_status')->map(CheckStatus::options())], + ['label' => __('offical_business.checked_at'), 'content' => '${checked_at}'], + ['label' => __('offical_business.check_remarks'), 'content' => '${check_remarks}'], + ]); + $table = amisMake()->Service() + ->id('offical-business-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('offical-business-detail'), + $this->failAction()->reload('offical-business-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('offical-business-detail')->title('')->onEvent([ + 'inited' => [ + 'actions' => [ + ['actionType' => 'reload', 'componentId' => 'offical-business-checklog-table'], + ] + ] + ])->body([$detail, amisMake()->Divider(), $table]); + } + + public function getMorphAlias() + { + return (new OfficalBusiness)->getMorphClass(); + } +} \ No newline at end of file diff --git a/app/Admin/Filters/OfficalBusinessFilter.php b/app/Admin/Filters/OfficalBusinessFilter.php new file mode 100644 index 0000000..04a354c --- /dev/null +++ b/app/Admin/Filters/OfficalBusinessFilter.php @@ -0,0 +1,39 @@ + [ + 'store_title' => 'title', + ], + 'employee' => [ + 'employee_name' => 'name', + 'employee_search' => 'search', + ], + ]; + + public function employeeId($key) + { + $this->where('employee_id', $key); + } + + public function checkStatus($key) + { + $this->where('check_status', $key); + } + + public function dateRange($key) + { + $dates = explode(',', $key); + $start = Carbon::createFromTimestamp(data_get($dates, 0, time()))->startOfDay(); + $end = Carbon::createFromTimestamp(data_get($dates, 1, time()))->endOfDay(); + $this->whereBetween('created_at', [$start, $end]); + } +} diff --git a/app/Admin/Services/OfficalBusinessService.php b/app/Admin/Services/OfficalBusinessService.php new file mode 100644 index 0000000..3f41bb7 --- /dev/null +++ b/app/Admin/Services/OfficalBusinessService.php @@ -0,0 +1,55 @@ +value('store_id'); + } + if (isset($data['date_range'])) { + $time = explode(',', $data['date_range']); + $start = Carbon::createFromTimestamp(data_get($time, 0))->startOfDay(); + $end = Carbon::createFromTimestamp(data_get($time, 1))->endOfDay(); + $data['start_at'] = $start; + $data['end_at'] = $end; + } + return $data; + } + + public function validate($data, $model = null) + { + // 验证申请时间是否重叠 + if (OfficalBusiness::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'], + 'start_at' => ['required'], + 'end_at' => ['required'], + 'address' => ['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/Services/OvertimeApplyService.php b/app/Admin/Services/OvertimeApplyService.php index 6d7cf88..1b877ef 100644 --- a/app/Admin/Services/OvertimeApplyService.php +++ b/app/Admin/Services/OvertimeApplyService.php @@ -37,6 +37,10 @@ class OvertimeApplyService extends BaseService public function validate($data, $model = null) { + // 验证申请时间是否重叠 + if (OvertimeApply::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'], 'start_at' => ['required'], diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 931017a..7d845fe 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -14,6 +14,7 @@ use App\Admin\Controllers\Hr\SignLogController; use App\Admin\Controllers\Hr\SignRepairController; use App\Admin\Controllers\Hr\HolidayController; use App\Admin\Controllers\Hr\OvertimeController; +use App\Admin\Controllers\Hr\OfficalBusinessController; use App\Admin\Controllers\Store\DeviceController; use App\Admin\Controllers\Store\EmployeeController as StoreEmployeeController; use App\Admin\Controllers\Store\StoreController; @@ -88,6 +89,8 @@ Route::group([ $router->resource('holiday', HolidayController::class); // 加班申请 $router->resource('overtime', OvertimeController::class); + // 出差报备 + $router->resource('business', OfficalBusinessController::class); }); /* diff --git a/app/Models/OfficalBusiness.php b/app/Models/OfficalBusiness.php new file mode 100644 index 0000000..e9e3e57 --- /dev/null +++ b/app/Models/OfficalBusiness.php @@ -0,0 +1,38 @@ + 'date:Y-m-d', + 'end_at' => 'date:Y-m-d', + 'check_status' => CheckStatus::class, + ]; + + public function modelFilter() + { + return App\Admin\Filters\OfficalBusinessFilter::class; + } + + public function store() + { + return $this->belongsTo(Store::class, 'store_id'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 1962756..dad06bd 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -34,6 +34,7 @@ class AppServiceProvider extends ServiceProvider \App\Models\EmployeeSignRepair::class, \App\Models\HolidayApply::class, \App\Models\OvertimeApply::class, + \App\Models\OfficalBusiness::class, ])->mapWithKeys(fn ($model) => [(new $model)->getTable() => $model])->all() ); } diff --git a/database/migrations/2024_04_02_120015_create_offical_business_table.php b/database/migrations/2024_04_02_120015_create_offical_business_table.php new file mode 100644 index 0000000..281aa3f --- /dev/null +++ b/database/migrations/2024_04_02_120015_create_offical_business_table.php @@ -0,0 +1,38 @@ +id(); + $table->foreignId('store_id')->comment('门店, stores.id'); + $table->foreignId('employee_id')->comment('申请人, employees.id'); + $table->timestamp('start_at')->comment('结束时间'); + $table->timestamp('end_at')->comment('开始时间'); + $table->string('address')->comment('目的地'); + $table->string('reason')->nullable()->comment('事由'); + + $table->unsignedInteger('check_status')->default(App\Enums\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('offical_business'); + } +}; diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php index 9115fac..1897887 100644 --- a/database/seeders/AdminPermissionSeeder.php +++ b/database/seeders/AdminPermissionSeeder.php @@ -140,6 +140,12 @@ class AdminPermissionSeeder extends Seeder 'uri' => '/hr/overtime', 'resource' => true, ], + 'business' => [ + 'name' => '出差报备', + 'icon' => '', + 'uri' => '/hr/business', + 'resource' => true, + ], ], ], diff --git a/database/seeders/WorkflowSeeder.php b/database/seeders/WorkflowSeeder.php index 54966c8..e6d0e79 100644 --- a/database/seeders/WorkflowSeeder.php +++ b/database/seeders/WorkflowSeeder.php @@ -21,6 +21,7 @@ class WorkflowSeeder extends Seeder ['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()], ['key' => 'overtime_apply', 'name' => '加班申请', 'config' => $config, 'created_at' => now(), 'updated_at' => now()], + ['key' => 'offical_business', 'name' => '出差报备', 'config' => $config, 'created_at' => now(), 'updated_at' => now()], ]); } } diff --git a/lang/zh_CN/offical_business.php b/lang/zh_CN/offical_business.php new file mode 100644 index 0000000..3b5f6d6 --- /dev/null +++ b/lang/zh_CN/offical_business.php @@ -0,0 +1,19 @@ + 'ID', + 'created_at' => '提交日期', + 'updated_at' => '更新时间', + + 'check_status' => '审核状态', + 'checked_at' => '审核通过时间', + 'check_remarks' => '审核未通过原因', + 'start_at' => '开始时间', + 'end_at' => '结束时间', + 'address' => '出差地点', + 'date' => '出差时间', + + 'store_id' => '门店', + 'employee_id' => '员工', + 'reason' => '出差事由', +];