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' => '业绩',
+ ],
+];