From 9dbb4b0af75638a7957d609b21ce0411837432ae Mon Sep 17 00:00:00 2001 From: Jing Li Date: Wed, 17 Apr 2024 20:50:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83=E4=BB=BB=E5=8A=A1=E8=AE=A1?= =?UTF-8?q?=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Admin/Controllers/Plan/PlanController.php | 105 ++++++++++++-- app/Admin/Controllers/Plan/TaskController.php | 4 + app/Admin/Services/Plan/PlanService.php | 130 ++++++++++++++++-- app/Admin/routes.php | 1 + app/Models/PlanPerformance.php | 4 +- app/Models/TaskHygiene.php | 53 +++++++ app/Models/TaskPerformance.php | 37 +++++ app/Providers/AppServiceProvider.php | 2 + ..._102006_create_task_performances_table.php | 32 +++++ ...4_17_133003_create_task_hygienes_table.php | 32 +++++ lang/zh_CN/plan.php | 26 ++++ routes/api.php | 2 +- 12 files changed, 406 insertions(+), 22 deletions(-) create mode 100644 app/Models/TaskHygiene.php create mode 100644 app/Models/TaskPerformance.php create mode 100644 database/migrations/2024_04_17_102006_create_task_performances_table.php create mode 100644 database/migrations/2024_04_17_133003_create_task_hygienes_table.php diff --git a/app/Admin/Controllers/Plan/PlanController.php b/app/Admin/Controllers/Plan/PlanController.php index 24f245b..6d9e28d 100644 --- a/app/Admin/Controllers/Plan/PlanController.php +++ b/app/Admin/Controllers/Plan/PlanController.php @@ -7,9 +7,12 @@ use App\Admin\Services\Plan\PlanService; use App\Enums\PlanStatus; use App\Enums\TaskStatus; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\DB; use Slowlyo\OwlAdmin\Admin; +use Slowlyo\OwlAdmin\Renderers\AjaxAction; use Slowlyo\OwlAdmin\Renderers\Form; use Slowlyo\OwlAdmin\Renderers\Page; +use Throwable; /** * @property PlanService $service @@ -44,7 +47,7 @@ class PlanController extends AdminController amis()->TextControl() ->name('name') ->label(__('plan.plan.name')) - ->placeholder('计划名称') + ->placeholder(__('plan.plan.name')) ->clearable(), amis()->SelectControl() @@ -69,6 +72,9 @@ class PlanController extends AdminController amis()->TableColumn('plan_status', __('plan.plan.status'))->type('mapping')->map(PlanStatus::labelMap()), amis()->TableColumn('created_at', __('plan.plan.created_at')), $this->rowActions([ + $this->rowPublishButton() + ->visible(Admin::user()->can('admin.plan.plans.publish')) + ->visibleOn('${plan_status == '.PlanStatus::Pending->value.'}'), $this->rowEditTypeButton('drawer', 'lg') ->visible(Admin::user()->can('admin.plan.plans.update')) ->visibleOn('${plan_status == '.PlanStatus::Pending->value.'}'), @@ -102,14 +108,14 @@ class PlanController extends AdminController // 业绩指标 amis()->MonthControl() ->name('plan_performance[month]') - ->label(__('plan.plan.month')) + ->label(__('plan.plan_performance.month')) ->value('${planable.month}') ->required() ->valueFormat('YYYY-MM') ->visibleOn('${planable_type == "plan_performances"}'), amis()->TreeSelectControl() ->name('plan_performance[store_category_id]') - ->label(__('plan.plan.store_category')) + ->label(__('plan.plan_performance.store_category')) ->source(admin_url('api/keywords/tree-list?parent_key=store_category')) ->labelField('name') ->valueField('key') @@ -119,7 +125,7 @@ class PlanController extends AdminController ->visibleOn('${planable_type == "plan_performances"}'), amis()->SelectControl() ->name('plan_performance[store_level_id]') - ->label(__('plan.plan.store_level')) + ->label(__('plan.plan_performance.store_level')) ->source(admin_url('api/keywords/tree-list?parent_key=store_level')) ->labelField('name') ->valueField('key') @@ -128,8 +134,8 @@ class PlanController extends AdminController ->visibleOn('${planable_type == "plan_performances"}'), amis()->NumberControl() ->name('plan_performance[performance]') - ->label(__('plan.plan.performance')) - ->placeholder(__('plan.plan.performance')) + ->label(__('plan.plan_performance.performance')) + ->placeholder(__('plan.plan_performance.performance')) ->value('${planable.performance}') ->precision(2) ->showSteps(false) @@ -139,14 +145,14 @@ class PlanController extends AdminController // 清洁卫生 amis()->MonthControl() ->name('plan_hygiene[month]') - ->label(__('plan.plan.month')) + ->label(__('plan.plan_hygiene.month')) ->value('${planable.month}') ->required() ->valueFormat('YYYY-MM') ->visibleOn('${planable_type == "plan_hygienes"}'), amis()->SelectControl() ->name('plan_hygiene[store_ids]') - ->label(__('plan.plan.store')) + ->label(__('plan.plan_hygiene.store')) ->value('${planable.store_ids}') ->multiple() ->joinValues(false) @@ -166,6 +172,24 @@ class PlanController extends AdminController ['label' => __('plan.plan.name'), 'content' => '${name}'], ['label' => __('plan.plan.type'), 'content' => amis()->Mapping()->name('planable_type')->map($this->planableTypeLabelMap)], ['label' => __('plan.plan.status'), 'content' => amis()->Mapping()->name('plan_status')->map(PlanStatus::labelMap())], + // 总账录入 + ['label' => __('plan.plan_ledger.date'), 'content' => '${planable.date}', 'visibleOn' => '${planable_type == "plan_ledgers"}'], + // 业绩指标 + ['label' => __('plan.plan_performance.month'), 'content' => '${planable.month}', 'visibleOn' => '${planable_type == "plan_performances"}'], + ['label' => __('plan.plan_performance.store_category'), 'content' => '${planable.store_category.name}', 'visibleOn' => '${planable_type == "plan_performances"}'], + ['label' => __('plan.plan_performance.store_level'), 'content' => '${planable.store_level.name}', 'visibleOn' => '${planable_type == "plan_performances"}'], + ['label' => __('plan.plan_performance.performance'), 'content' => '${planable.performance}', 'visibleOn' => '${planable_type == "plan_performances"}'], + // 清洁卫生 + ['label' => __('plan.plan_hygiene.month'), 'content' => '${planable.month}', 'visibleOn' => '${planable_type == "plan_hygienes"}'], + [ + 'label' => __('plan.plan_hygiene.store'), + 'content' => amis()->Each() + ->name('planable.stores') + ->items( + amis()->Tpl()->tpl('${title}') + ), + 'visibleOn' => '${planable_type == "plan_hygienes"}', + ], ]), amis()->Divider(), @@ -184,6 +208,71 @@ class PlanController extends AdminController amis()->TableColumn('created_at', __('plan.task.created_at')), ]) ->visibleOn('${planable_type == "plan_ledgers"}'), + + // 业绩指标 + amis()->CRUDTable() + ->api(admin_url('api/tasks?plan_id=${id}')) + ->headerToolbar([ + amis('reload')->align('right'), + ]) + ->columns([ + amis()->TableColumn('id', __('plan.task.id')), + amis()->TableColumn('name', __('plan.task.name')), + amis()->TableColumn('taskable.month', __('plan.task_performance.month')), + amis()->TableColumn('taskable.store.title', __('plan.task_performance.store')), + amis()->TableColumn('taskable.store_master.name', __('plan.task_performance.store_master')), + amis()->TableColumn('taskable.actual_performance', __('plan.task_performance.actual_performance')), + amis()->TableColumn('taskable.expected_performance', __('plan.task_performance.expected_performance')), + amis()->TableColumn('task_status', __('plan.task.status'))->type('mapping')->map(TaskStatus::labelMap()), + amis()->TableColumn('completed_at', __('plan.task.completed_at')), + amis()->TableColumn('created_at', __('plan.task.created_at')), + ]) + ->visibleOn('${planable_type == "plan_performances"}'), + + // 清洁卫生 + amis()->CRUDTable() + ->api(admin_url('api/tasks?plan_id=${id}')) + ->headerToolbar([ + amis('reload')->align('right'), + ]) + ->columns([ + amis()->TableColumn('id', __('plan.task.id')), + amis()->TableColumn('name', __('plan.task.name')), + amis()->TableColumn('taskable.month', __('plan.plan_hygiene.month')), + amis()->TableColumn('taskable.store.title', __('plan.plan_hygiene.store')), + amis()->TableColumn('taskable.store_master.name', __('plan.plan_hygiene.store_master')), + amis()->TableColumn('task_status', __('plan.task.status'))->type('mapping')->map(TaskStatus::labelMap()), + amis()->TableColumn('completed_at', __('plan.task.completed_at')), + amis()->TableColumn('created_at', __('plan.task.created_at')), + ]) + ->visibleOn('${planable_type == "plan_hygienes"}'), ]); } + + /** + * 发布任务计划 + */ + public function publish($id) + { + try { + DB::transaction(fn () => $this->service->publish($id)); + } catch (Throwable $th) { + return $this->renderException($th); + } + + return $this->response()->successMessage('发布成功'); + } + + /** + * 行发布按钮 + */ + protected function rowPublishButton(): AjaxAction + { + return amis()->AjaxAction() + ->label('发布') + ->icon('fa fa-send-o') + ->level('link') + ->confirmText('是否发布选中的任务计划?') + ->api('post:' . admin_url('/plan/plans/${id}/publish')); + } } diff --git a/app/Admin/Controllers/Plan/TaskController.php b/app/Admin/Controllers/Plan/TaskController.php index f0a81f2..17d74fe 100644 --- a/app/Admin/Controllers/Plan/TaskController.php +++ b/app/Admin/Controllers/Plan/TaskController.php @@ -6,7 +6,9 @@ use App\Admin\Controllers\AdminController; use App\Admin\Filters\TaskFilter; use App\Admin\Services\Plan\TaskService; use App\Models\Task; +use App\Models\TaskHygiene; use App\Models\TaskLedger; +use App\Models\TaskPerformance; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Http\Request; @@ -24,6 +26,8 @@ class TaskController extends AdminController 'taskable' => function (MorphTo $morphTo) { $morphTo->morphWith([ TaskLedger::class => ['store', 'storeMaster'], + TaskPerformance::class => ['store', 'storeMaster'], + TaskHygiene::class => ['store', 'storeMaster'], ]); }, ]) diff --git a/app/Admin/Services/Plan/PlanService.php b/app/Admin/Services/Plan/PlanService.php index 6e726da..f5d2760 100644 --- a/app/Admin/Services/Plan/PlanService.php +++ b/app/Admin/Services/Plan/PlanService.php @@ -3,13 +3,20 @@ namespace App\Admin\Services\Plan; use App\Admin\Filters\PlanFilter; +use App\Admin\Filters\StoreFilter; use App\Admin\Services\BaseService; use App\Enums\PlanStatus; +use App\Enums\TaskStatus; use App\Models\Keyword; use App\Models\Plan; use App\Models\PlanHygiene; use App\Models\PlanPerformance; +use App\Models\Store; +use App\Models\TaskHygiene; +use App\Models\TaskPerformance; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; @@ -49,11 +56,11 @@ class PlanService extends BaseService attributes: [ 'name' => __('plan.plan.name'), 'planable_type' => __('plan.plan.type'), - 'plan_performance.month' => __('plan.plan.month'), - 'plan_performance.store_category_id' => __('plan.plan.store_category_id'), - 'plan_performance.store_level_id' => __('plan.plan.store_level_id'), - 'plan_performance.performance' => __('plan.plan.performance'), - 'plan_hygiene.month' => __('plan.plan.month'), + 'plan_performance.month' => __('plan.plan_performance.month'), + 'plan_performance.store_category_id' => __('plan.plan_performance.store_category_id'), + 'plan_performance.store_level_id' => __('plan.plan_performance.store_level_id'), + 'plan_performance.performance' => __('plan.plan_performance.performance'), + 'plan_hygiene.month' => __('plan.plan_hygiene.month'), ], ); @@ -106,11 +113,11 @@ class PlanService extends BaseService rules: $rules, attributes: [ 'name' => __('plan.plan.name'), - 'plan_performance.month' => __('plan.plan.month'), - 'plan_performance.store_category_id' => __('plan.plan.store_category_id'), - 'plan_performance.store_level_id' => __('plan.plan.store_level_id'), - 'plan_performance.performance' => __('plan.plan.performance'), - 'plan_hygiene.month' => __('plan.plan.month'), + 'plan_performance.month' => __('plan.plan_performance.month'), + 'plan_performance.store_category_id' => __('plan.plan_performance.store_category_id'), + 'plan_performance.store_level_id' => __('plan.plan_performance.store_level_id'), + 'plan_performance.performance' => __('plan.plan_performance.performance'), + 'plan_hygiene.month' => __('plan.plan_hygiene.month'), ], ); @@ -132,6 +139,101 @@ class PlanService extends BaseService return true; } + public function publish($id) + { + /** @var \App\Models\Plan */ + $plan = Plan::findOrFail($id); + + if ($plan->isPublished()) { + admin_abort('任务计划已发布'); + } + + switch (get_class($planable = $plan->planable)) { + // 清洁卫生 + case PlanHygiene::class: + // 任务计划月份 + $month = Carbon::createFromFormat('Y-m', $planable->month); + // 任务开始时间 + $startAt = $month->copy()->startOfMonth(); + // 任务结束时间 + $endAt = $month->copy()->endOfMonth(); + + /** @var \Illuminate\Database\Eloquent\Collection */ + $stores = Store::findMany($planable->store_ids); + + foreach ($stores as $store) { + $taskable = TaskHygiene::create([ + 'month' => $planable->month, + 'store_id' => $store->id, + 'store_master_id' => $store->master_id, + ]); + $taskable->task()->create([ + 'plan_id' => $plan->id, + 'name' => '清洁任务', + 'start_at' => $startAt, + 'end_at' => $endAt, + 'task_status' => TaskStatus::Pending, + ]); + } + break; + + // 业绩指标 + case PlanPerformance::class: + // 任务计划月份 + $month = Carbon::createFromFormat('Y-m', $planable->month); + // 任务开始时间 + $startAt = $month->copy()->startOfMonth(); + // 任务结束时间 + $endAt = $month->copy()->endOfMonth(); + + $input = [ + 'category_id' => $planable->store_category_id, + 'level_id' => $planable->store_level_id, + ]; + + /** @var \Illuminate\Database\Eloquent\Collection */ + $stores = Store::filter($input, StoreFilter::class)->get(['id', 'master_id']); + + foreach ($stores as $store) { + $taskable = TaskPerformance::create([ + 'month' => $planable->month, + 'store_id' => $store->id, + 'store_master_id' => $store->master_id, + 'expected_performance' => $planable->performance, + 'actual_performance' => 0, + ]); + $taskable->task()->create([ + 'plan_id' => $plan->id, + 'name' => '业绩指标', + 'start_at' => $startAt, + 'end_at' => $endAt, + 'task_status' => TaskStatus::Pending, + ]); + } + break; + + default: + admin_abort('未知计划类型'); + break; + } + + $plan->update([ + 'plan_status' => PlanStatus::Published, + ]); + } + + public function getDetail($id) + { + if ($plan = parent::getDetail($id)) { + if ($plan->planable instanceof PlanHygiene) { + $plan->planable->setAttribute( + 'stores', Store::findMany($plan->planable->store_ids) + ); + } + } + return $plan; + } + public function preDelete(array $ids): void { /** @var \Illuminate\Database\Eloquent\Collection */ @@ -145,7 +247,13 @@ class PlanService extends BaseService public function addRelations($query, string $scene = 'list') { if (in_array($scene, ['edit', 'detail'])) { - $query->with(['planable']); + $query->with([ + 'planable' => function (MorphTo $morphTo) { + $morphTo->morphWith([ + PlanPerformance::class => ['storeCategory', 'storeLevel'], + ]); + }, + ]); } } } diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 69c4be4..2d4375c 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -153,6 +153,7 @@ Route::group([ ], function (Router $router) { // 任务计划 $router->resource('plans', PlanController::class); + $router->post('/plans/{plan}/publish', [PlanController::class, 'publish'])->name('plans.publish'); }); /* diff --git a/app/Models/PlanPerformance.php b/app/Models/PlanPerformance.php index b2a372e..a74302d 100644 --- a/app/Models/PlanPerformance.php +++ b/app/Models/PlanPerformance.php @@ -19,12 +19,12 @@ class PlanPerformance extends Model return $this->morphOne(Plan::class, 'planable'); } - public function category() + public function storeCategory() { return $this->belongsTo(Keyword::class, 'store_category_id', 'key'); } - public function level() + public function storeLevel() { return $this->belongsTo(Keyword::class, 'store_level_id', 'key'); } diff --git a/app/Models/TaskHygiene.php b/app/Models/TaskHygiene.php new file mode 100644 index 0000000..b76213d --- /dev/null +++ b/app/Models/TaskHygiene.php @@ -0,0 +1,53 @@ +morphOne(Task::class, 'taskable'); + } + + public function store(): BelongsTo + { + return $this->belongsTo(Store::class); + } + + public function storeMaster(): BelongsTo + { + return $this->belongsTo(Employee::class, 'store_master_id'); + } + + protected function photos(): Attribute + { + return Attribute::make( + get: function (mixed $value) { + if (! is_array($photos = json_decode($value ?? '', true))) { + $photos = []; + } + + return $photos; + }, + set: fn (mixed $value) => json_encode(is_array($value) ? $value : []), + ); + } +} diff --git a/app/Models/TaskPerformance.php b/app/Models/TaskPerformance.php new file mode 100644 index 0000000..63fcaac --- /dev/null +++ b/app/Models/TaskPerformance.php @@ -0,0 +1,37 @@ +morphOne(Task::class, 'taskable'); + } + + public function store(): BelongsTo + { + return $this->belongsTo(Store::class); + } + + public function storeMaster(): BelongsTo + { + return $this->belongsTo(Employee::class, 'store_master_id'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c9fc7c3..8e2feae 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -45,7 +45,9 @@ class AppServiceProvider extends ServiceProvider \App\Models\PlanPerformance::class, \App\Models\Reimbursement::class, \App\Models\StoreMasterCommission::class, + \App\Models\TaskHygiene::class, \App\Models\TaskLedger::class, + \App\Models\TaskPerformance::class, \App\Models\EmployeePromotion::class, \App\Models\Agreement::class, ])->mapWithKeys(fn ($model) => [(new $model)->getTable() => $model])->all() diff --git a/database/migrations/2024_04_17_102006_create_task_performances_table.php b/database/migrations/2024_04_17_102006_create_task_performances_table.php new file mode 100644 index 0000000..54a2fca --- /dev/null +++ b/database/migrations/2024_04_17_102006_create_task_performances_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('month')->comment('月份: 2024-04'); + $table->foreignId('store_id')->comment('门店'); + $table->foreignId('store_master_id')->comment('店长'); + $table->decimal('expected_performance', 10, 2)->comment('预期业绩'); + $table->decimal('actual_performance', 10, 2)->comment('实际业绩'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('task_performances'); + } +}; diff --git a/database/migrations/2024_04_17_133003_create_task_hygienes_table.php b/database/migrations/2024_04_17_133003_create_task_hygienes_table.php new file mode 100644 index 0000000..a76734e --- /dev/null +++ b/database/migrations/2024_04_17_133003_create_task_hygienes_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('month')->comment('月份: 2024-04'); + $table->foreignId('store_id')->comment('门店'); + $table->foreignId('store_master_id')->comment('店长'); + $table->string('description')->nullable()->comment('描述'); + $table->json('photos')->nullable()->comment('图片'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('task_hygienes'); + } +}; diff --git a/lang/zh_CN/plan.php b/lang/zh_CN/plan.php index 0e86ad2..d6de7e8 100644 --- a/lang/zh_CN/plan.php +++ b/lang/zh_CN/plan.php @@ -8,6 +8,13 @@ return [ 'status' => '计划状态', 'created_at' => '创建时间', 'updated_at' => '更新时间', + ], + + 'plan_ledger' => [ + 'date' => '日期', + ], + + 'plan_performance' => [ 'month' => '月份', 'store' => '门店', 'store_category' => '门店分类', @@ -15,6 +22,11 @@ return [ 'performance' => '业绩', ], + 'plan_hygiene' => [ + 'month' => '月份', + 'store' => '门店', + ], + 'task' => [ 'id' => 'ID', 'name' => '任务名称', @@ -28,4 +40,18 @@ return [ 'store' => '门店', 'store_master' => '店长', ], + + 'task_performance' => [ + 'month' => '月份', + 'store' => '门店', + 'store_master' => '店长', + 'actual_performance' => '实际业绩', + 'expected_performance' => '目标业绩', + ], + + 'plan_hygiene' => [ + 'month' => '月份', + 'store' => '门店', + 'store_master' => '店长', + ], ]; diff --git a/routes/api.php b/routes/api.php index 50e8d82..f843b64 100644 --- a/routes/api.php +++ b/routes/api.php @@ -89,7 +89,7 @@ Route::group([ // 合同管理 Route::apiResource('agreements', \App\Http\Controllers\Api\AgreementController::class); - + // 审核流程 Route::get('workflow', [\App\Http\Controllers\Api\WorkflowController::class, 'index']); Route::get('workflow/{id}', [\App\Http\Controllers\Api\WorkflowController::class, 'show']);