diff --git a/app/Admin/Controllers/Plan/PlanController.php b/app/Admin/Controllers/Plan/PlanController.php index 3509535..bdacba5 100644 --- a/app/Admin/Controllers/Plan/PlanController.php +++ b/app/Admin/Controllers/Plan/PlanController.php @@ -2,8 +2,153 @@ namespace App\Admin\Controllers\Plan; -use Slowlyo\OwlAdmin\Controllers\AdminController; +use App\Admin\Controllers\AdminController; +use App\Admin\Services\Plan\PlanService; +use App\Enums\PlanStatus; +use Illuminate\Support\Arr; +use Slowlyo\OwlAdmin\Admin; +use Slowlyo\OwlAdmin\Renderers\Form; +use Slowlyo\OwlAdmin\Renderers\Page; +/** + * @property PlanService $service + */ class PlanController extends AdminController { + protected string $serviceName = PlanService::class; + + protected array $planableTypes = [ + 'plan_ledgers' => '总账录入', + 'plan_performances' => '业绩指标', + 'plan_hygienes' => '清洁卫生', + ]; + + protected array $planableTypeLabelMap = [ + 'plan_ledgers' => '总账录入', + 'plan_performances' => '业绩指标', + 'plan_hygienes' => '清洁卫生', + ]; + + public function list(): Page + { + $crud = $this->baseCRUD() + ->headerToolbar([ + $this->createTypeButton('drawer', 'lg') + ->visible(Admin::user()->can('admin.plan.plans.create')), + ...$this->baseHeaderToolBar(), + ]) + ->bulkActions([]) + ->filter($this->baseFilter()->body([ + amis()->GroupControl()->mode('horizontal')->body([ + amis()->TextControl() + ->name('name') + ->label(__('plan.plan.name')) + ->placeholder('计划名称') + ->clearable(), + + amis()->SelectControl() + ->name('planable_type') + ->label(__('plan.plan.type')) + ->multiple() + ->options($this->planableTypes) + ->clearable(), + + amis()->SelectControl() + ->name('plan_status') + ->label(__('plan.plan.status')) + ->multiple() + ->options(PlanStatus::options()) + ->clearable(), + ]), + ])) + ->columns([ + amis()->TableColumn('id', __('plan.plan.id')), + amis()->TableColumn('name', __('plan.plan.name')), + amis()->TableColumn('planable_type', __('plan.plan.type'))->type('mapping')->map($this->planableTypeLabelMap), + amis()->TableColumn('plan_status', __('plan.plan.status'))->type('mapping')->map(PlanStatus::labelMap()), + amis()->TableColumn('created_at', __('plan.plan.created_at')), + $this->rowActions([ + $this->rowEditTypeButton('drawer', 'lg') + ->visible(Admin::user()->can('admin.plan.plans.update')) + ->visibleOn('${plan_status == '.PlanStatus::Pending->value.'}'), + $this->rowDeleteButton() + ->visible(Admin::user()->can('admin.plan.plans.delete')) + ->visibleOn('${plan_status == '.PlanStatus::Pending->value.'}'), + $this->rowShowButton() + ->visible(Admin::user()->can('admin.plan.plans.view')), + ]), + ]); + + return $this->baseList($crud); + } + + public function form(): Form + { + return $this->baseForm()->title('')->body([ + amis()->SelectControl() + ->name('planable_type') + ->label(__('plan.plan.type')) + ->options(Arr::except($this->planableTypes, ['plan_ledgers'])) + ->required() + ->disabledOn('${planable_id > 0}'), + + amis()->TextControl() + ->name('name') + ->label(__('plan.plan.name')) + ->placeholder(__('plan.plan.name')) + ->required(), + + // 业绩指标 + amis()->MonthControl() + ->name('plan_performance[month]') + ->label(__('plan.plan.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_id')) + ->source(admin_url('api/keywords/tree-list?parent_key=store_category')) + ->labelField('name') + ->valueField('key') + ->onlyLeaf(true) + ->value('${planable.store_category_id}') + ->required() + ->visibleOn('${planable_type == "plan_performances"}'), + amis()->SelectControl() + ->name('plan_performance[store_level_id]') + ->label(__('plan.plan.store_level_id')) + ->source(admin_url('api/keywords/tree-list?parent_key=store_level')) + ->labelField('name') + ->valueField('key') + ->value('${planable.store_level_id}') + ->clearable() + ->visibleOn('${planable_type == "plan_performances"}'), + amis()->NumberControl() + ->name('plan_performance[performance]') + ->label(__('plan.plan.performance')) + ->placeholder(__('plan.plan.performance')) + ->value('${planable.performance}') + ->precision(2) + ->showSteps(false) + ->required() + ->visibleOn('${planable_type == "plan_performances"}'), + + // 清洁卫生 + amis()->MonthControl() + ->name('plan_hygiene[month]') + ->label(__('plan.plan.month')) + ->value('${planable.month}') + ->required() + ->valueFormat('YYYY-MM') + ->visibleOn('${planable_type == "plan_hygienes"}'), + ]); + } + + public function detail(): Form + { + return $this->baseDetail()->title('')->body([ + ]); + } } diff --git a/app/Admin/Filters/PlanFilter.php b/app/Admin/Filters/PlanFilter.php new file mode 100644 index 0000000..bbeb327 --- /dev/null +++ b/app/Admin/Filters/PlanFilter.php @@ -0,0 +1,23 @@ +whereLike('name', $name); + } + + public function planableType($planableType) + { + $this->whereIn('planable_type', explode(',', $planableType)); + } + + public function planStatus($planStatus) + { + $this->whereIn('plan_status', explode(',', $planStatus)); + } +} diff --git a/app/Admin/Services/BaseService.php b/app/Admin/Services/BaseService.php index 3759a8b..eb8acc5 100644 --- a/app/Admin/Services/BaseService.php +++ b/app/Admin/Services/BaseService.php @@ -10,8 +10,6 @@ use Slowlyo\OwlAdmin\Services\AdminService; */ class BaseService extends AdminService { - protected array $withRelationships = []; - protected string $modelFilterName = ''; protected bool $modelSortAble = false; @@ -52,15 +50,11 @@ class BaseService extends AdminService public function listQuery() { - $model = $this->getModel(); - $filter = $this->getModelFilter(); - $query = $this->query(); - if ($this->withRelationships) { - $query->with($this->withRelationships); - } - if ($filter) { + $this->addRelations($query); + + if ($filter = $this->getModelFilter()) { $query->filter(request()->input(), $filter); } @@ -73,11 +67,6 @@ class BaseService extends AdminService return $query; } - public function getDetail($id) - { - return $this->query()->with($this->withRelationships)->find($id); - } - public function store($data): bool { $data = $this->resloveData($data); @@ -160,4 +149,14 @@ class BaseService extends AdminService public function preDelete(array $ids): void { } + + /** + * {@inheritdoc} + */ + public function addRelations($query, string $scene = 'list') + { + if (property_exists($this, 'withRelationships')) { + $query->with($this->withRelationships); + } + } } diff --git a/app/Admin/Services/Plan/PlanService.php b/app/Admin/Services/Plan/PlanService.php new file mode 100644 index 0000000..6e1e826 --- /dev/null +++ b/app/Admin/Services/Plan/PlanService.php @@ -0,0 +1,151 @@ + ['bail', 'required', 'max:255'], + 'planable_type' => ['bail', 'required', Rule::in(['plan_performances', 'plan_hygienes'])], + ], + match ($data['planable_type'] ?? '') { + 'plan_performances' => [ + 'plan_performance.month' => ['bail', 'required', 'date_format:Y-m'], + 'plan_performance.store_category_id' => ['bail', 'required', Rule::exists(Keyword::class, 'key')], + 'plan_performance.store_level_id' => ['bail', 'nullable', Rule::exists(Keyword::class, 'key')], + 'plan_performance.performance' => ['bail', 'required', 'numeric', 'min:0'], + ], + 'plan_hygienes' => [ + 'plan_hygiene.month' => ['bail', 'required', 'date_format:Y-m'], + ], + }, + ); + + Validator::validate( + data: $data, + rules: $rules, + 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'), + ], + ); + + switch ($data['planable_type']) { + case 'plan_performances': + $planable = PlanPerformance::create($data['plan_performance']); + break; + + case 'plan_performances': + $planable = PlanHygiene::create($data['plan_hygiene']); + break; + } + + $planable->plan()->create([ + 'name' => $data['name'], + 'plan_status' => PlanStatus::Pending, + ]); + + return true; + } + + public function update($primaryKey, $data): bool + { + /** @var \App\Models\Plan|null */ + $plan = Plan::findOrFail($primaryKey); + + if ($plan->isPublished()) { + admin_abort('任务计划已发布'); + } + + $rules = array_merge( + [ + 'name' => ['filled', 'max:255'], + ], + match ($plan->planable_type) { + 'plan_performances' => [ + 'plan_performance.month' => ['bail', 'required', 'date_format:Y-m'], + 'plan_performance.store_category_id' => ['bail', 'required', Rule::exists(Keyword::class, 'key')], + 'plan_performance.store_level_id' => ['bail', 'nullable', Rule::exists(Keyword::class, 'key')], + 'plan_performance.performance' => ['bail', 'required', 'numeric', 'min:0'], + ], + 'plan_hygienes' => [ + 'plan_hygiene.month' => ['bail', 'required', 'date_format:Y-m'], + ], + }, + ); + + Validator::validate( + data: $data, + 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'), + ], + ); + + if (array_key_exists('name', $data)) { + $plan->name = $data['name']; + } + $plan->save(); + + switch ($plan->planable_type) { + case 'plan_performances': + $plan->planable->update($data['plan_performance']); + break; + + case 'plan_hygienes': + $plan->planable->update($data['plan_hygiene']); + break; + } + + return true; + } + + public function preDelete(array $ids): void + { + /** @var \Illuminate\Database\Eloquent\Collection */ + $plans = Plan::findMany($ids); + + if ($plans->contains(fn (Plan $plan) => $plan->isPublished())) { + admin_abort('不能删除已发布的任务计划'); + } + } + + public function addRelations($query, string $scene = 'list') + { + if (in_array($scene, ['edit', 'detail'])) { + $query->with(['planable']); + } + } +} diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 7bafef6..a64f5f5 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -1,5 +1,6 @@ resource('plan/plans', \App\Admin\Controllers\Plan\PlanController::class); + $router->group([ + 'prefix' => 'plan', + 'as' => 'plan.', + ], function (Router $router) { + // 任务计划 + $router->resource('plans', PlanController::class); + }); /* |-------------------------------------------------------------------------- diff --git a/app/Enums/PlanStatus.php b/app/Enums/PlanStatus.php new file mode 100644 index 0000000..d644c7b --- /dev/null +++ b/app/Enums/PlanStatus.php @@ -0,0 +1,30 @@ +value]; + } + + public static function options(): array + { + return [ + self::Pending->value => '待发布', + self::Published->value => '已发布', + ]; + } + + public static function labelMap(): array + { + return [ + self::Pending->value => ''.self::Pending->text().'', + self::Published->value => ''.self::Published->text().'', + ]; + } +} diff --git a/app/Models/Plan.php b/app/Models/Plan.php new file mode 100644 index 0000000..48445c0 --- /dev/null +++ b/app/Models/Plan.php @@ -0,0 +1,40 @@ + PlanStatus::Pending, + ]; + + protected $casts = [ + 'plan_status' => PlanStatus::class, + ]; + + protected $fillable = [ + 'name', + 'plan_status', + 'planable_id', + 'planable_type', + ]; + + public function planable(): MorphTo + { + return $this->morphTo(); + } + + public function isPublished(): bool + { + return $this->plan_status === PlanStatus::Published; + } +} diff --git a/app/Models/PlanHygiene.php b/app/Models/PlanHygiene.php new file mode 100644 index 0000000..92375ae --- /dev/null +++ b/app/Models/PlanHygiene.php @@ -0,0 +1,21 @@ +morphOne(Plan::class, 'planable'); + } +} diff --git a/app/Models/PlanLedger.php b/app/Models/PlanLedger.php new file mode 100644 index 0000000..befaae7 --- /dev/null +++ b/app/Models/PlanLedger.php @@ -0,0 +1,27 @@ + 'date', + ]; + + protected $fillable = [ + 'date', + ]; + + public function plan(): MorphOne + { + return $this->morphOne(Plan::class, 'planable'); + } +} + diff --git a/app/Models/PlanPerformance.php b/app/Models/PlanPerformance.php new file mode 100644 index 0000000..b2a372e --- /dev/null +++ b/app/Models/PlanPerformance.php @@ -0,0 +1,31 @@ +morphOne(Plan::class, 'planable'); + } + + public function category() + { + return $this->belongsTo(Keyword::class, 'store_category_id', 'key'); + } + + public function level() + { + return $this->belongsTo(Keyword::class, 'store_level_id', 'key'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 9b5fad0..e3bfef3 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -40,6 +40,9 @@ class AppServiceProvider extends ServiceProvider \App\Models\OvertimeApply::class, \App\Models\OfficalBusiness::class, \App\Models\Ledger::class, + \App\Models\PlanHygiene::class, + \App\Models\PlanLedger::class, + \App\Models\PlanPerformance::class, \App\Models\Reimbursement::class, \App\Models\StoreMasterCommission::class, \App\Models\EmployeePromotion::class, diff --git a/database/migrations/2024_04_08_223111_create_plans_table.php b/database/migrations/2024_04_08_223111_create_plans_table.php new file mode 100644 index 0000000..ac02e8a --- /dev/null +++ b/database/migrations/2024_04_08_223111_create_plans_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('name')->comment('名称'); + $table->morphs('planable'); + $table->tinyInteger('plan_status')->comment('状态'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('plans'); + } +}; diff --git a/database/migrations/2024_04_14_153020_create_plan_ledgers_table.php b/database/migrations/2024_04_14_153020_create_plan_ledgers_table.php new file mode 100644 index 0000000..8482eaa --- /dev/null +++ b/database/migrations/2024_04_14_153020_create_plan_ledgers_table.php @@ -0,0 +1,30 @@ +id(); + $table->date('date')->comment('日期'); + $table->timestamps(); + + $table->unique(['date']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('plan_ledgers'); + } +}; diff --git a/database/migrations/2024_04_14_203014_create_plan_performances_table.php b/database/migrations/2024_04_14_203014_create_plan_performances_table.php new file mode 100644 index 0000000..819b687 --- /dev/null +++ b/database/migrations/2024_04_14_203014_create_plan_performances_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('month')->comment('月份: 2024-04'); + $table->string('store_category_id')->nullable()->comment('门店分类'); + $table->string('store_level_id')->nullable()->comment('门店等级'); + $table->decimal('performance', 10, 2)->comment('业绩'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('plan_performances'); + } +}; diff --git a/database/migrations/2024_04_14_211927_create_plan_hygienes_table.php b/database/migrations/2024_04_14_211927_create_plan_hygienes_table.php new file mode 100644 index 0000000..5ac49c5 --- /dev/null +++ b/database/migrations/2024_04_14_211927_create_plan_hygienes_table.php @@ -0,0 +1,28 @@ +id(); + $table->string('month')->comment('月份: 2024-04'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('plan_hygienes'); + } +}; diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php index 1839245..3ff14a3 100644 --- a/database/seeders/AdminPermissionSeeder.php +++ b/database/seeders/AdminPermissionSeeder.php @@ -155,6 +155,26 @@ class AdminPermissionSeeder extends Seeder ], ], + /* + |-------------------------------------------------------------------------- + | 计划任务 + |-------------------------------------------------------------------------- + */ + 'plan' => [ + 'name' => '任务计划', + 'icon' => 'flowbite:user-settings-solid', + 'uri' => '/hr', + 'children' => [ + 'plans' => [ + 'name' => '任务计划', + 'icon' => '', + 'uri' => '/plan/plans', + 'resource' => true, + 'children' => [], + ], + ], + ], + /* |-------------------------------------------------------------------------- | 投诉意见 diff --git a/lang/zh_CN/plan.php b/lang/zh_CN/plan.php new file mode 100644 index 0000000..4d40615 --- /dev/null +++ b/lang/zh_CN/plan.php @@ -0,0 +1,16 @@ + [ + 'id' => 'ID', + 'name' => '计划名称', + 'type' => '计划类型', + 'status' => '计划状态', + 'created_at' => '创建时间', + 'updated_at' => '更新时间', + 'month' => '月份', + 'store_category_id' => '门店分类', + 'store_level_id' => '门店等级', + 'performance' => '业绩', + ], +];