generated from liutk/owl-admin-base
任务管理
parent
b5adac501f
commit
2e86d6fd52
|
|
@ -13,6 +13,8 @@ use Illuminate\Support\Arr;
|
|||
use Illuminate\Support\Facades\DB;
|
||||
use Slowlyo\OwlAdmin\Admin;
|
||||
use Slowlyo\OwlAdmin\Renderers\AjaxAction;
|
||||
use Slowlyo\OwlAdmin\Renderers\Drawer;
|
||||
use Slowlyo\OwlAdmin\Renderers\DrawerAction;
|
||||
use Slowlyo\OwlAdmin\Renderers\Form;
|
||||
use Slowlyo\OwlAdmin\Renderers\Page;
|
||||
use Throwable;
|
||||
|
|
@ -196,8 +198,8 @@ class PlanController extends AdminController
|
|||
amis()->CRUDTable()
|
||||
->api(admin_url('api/tasks?plan_id=${id}'))
|
||||
->columns([
|
||||
amis()->TableColumn('id', __('plan.task.id')),
|
||||
amis()->TableColumn('name', __('plan.task.name')),
|
||||
// amis()->TableColumn('id', __('plan.task.id')),
|
||||
// amis()->TableColumn('name', __('plan.task.name')),
|
||||
amis()->TableColumn('taskable.date', __('plan.task_ledger.date')),
|
||||
amis()->TableColumn('taskable.store.title', __('plan.task_ledger.store')),
|
||||
amis()->TableColumn('taskable.store_master.name', __('plan.task_ledger.store_master')),
|
||||
|
|
@ -209,13 +211,16 @@ class PlanController extends AdminController
|
|||
|
||||
// 业绩指标
|
||||
amis()->CRUDTable()
|
||||
->name('task-performance-table')
|
||||
->api(admin_url('api/tasks?plan_id=${id}'))
|
||||
->headerToolbar([
|
||||
$this->taskCreateButton()
|
||||
->visible(Admin::user()->can('admin.plan.plans.task_create')),
|
||||
amis('reload')->align('right'),
|
||||
])
|
||||
->columns([
|
||||
amis()->TableColumn('id', __('plan.task.id')),
|
||||
amis()->TableColumn('name', __('plan.task.name')),
|
||||
// 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')),
|
||||
|
|
@ -224,24 +229,37 @@ class PlanController extends AdminController
|
|||
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')),
|
||||
$this->rowActions([
|
||||
$this->taskRowEditButton()
|
||||
->visible(Admin::user()->can('admin.plan.plans.task_update')),
|
||||
$this->taskRowDeleteButton()
|
||||
->visible(Admin::user()->can('admin.plan.plans.task_delete')),
|
||||
]),
|
||||
])
|
||||
->visibleOn('${planable_type == "'.$planableTypePerformance.'"}'),
|
||||
|
||||
// 清洁卫生
|
||||
amis()->CRUDTable()
|
||||
->name('task-hygiene-table')
|
||||
->api(admin_url('api/tasks?plan_id=${id}'))
|
||||
->headerToolbar([
|
||||
$this->taskCreateButton()
|
||||
->visible(Admin::user()->can('admin.plan.plans.task_create')),
|
||||
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('id', __('plan.task.id')),
|
||||
// amis()->TableColumn('name', __('plan.task.name')),
|
||||
amis()->TableColumn('taskable.month', __('plan.task_hygiene.month')),
|
||||
amis()->TableColumn('taskable.store.title', __('plan.task_hygiene.store')),
|
||||
amis()->TableColumn('taskable.store_master.name', __('plan.task_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')),
|
||||
$this->rowActions([
|
||||
$this->taskRowDeleteButton()
|
||||
->visible(Admin::user()->can('admin.plan.plans.task_delete')),
|
||||
]),
|
||||
])
|
||||
->visibleOn('${planable_type == "'.$planableTypeHygiene.'"}'),
|
||||
]);
|
||||
|
|
@ -274,6 +292,111 @@ class PlanController extends AdminController
|
|||
->api('post:' . admin_url('/plan/plans/${id}/publish'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务 - 创建按钮
|
||||
*/
|
||||
protected function taskCreateButton(): DrawerAction
|
||||
{
|
||||
$planableTypePerformance = (new PlanPerformance())->getMorphClass();
|
||||
$planableTypeHygiene = (new PlanHygiene())->getMorphClass();
|
||||
|
||||
$form = amis()->Form()
|
||||
->title('')
|
||||
->api('post:'.admin_url('/plan/tasks'))
|
||||
->redirect('')
|
||||
->body([
|
||||
amis()->HiddenControl('plan_id')->value('${id}'),
|
||||
// 业绩指标
|
||||
amis()->SelectControl('task_performance[store_id]', __('plan.task_performance.store'))
|
||||
->source(admin_url('api/stores'))
|
||||
->labelField('title')
|
||||
->valueField('id')
|
||||
->clearable()
|
||||
->required()
|
||||
->visibleOn('${planable_type == "'.$planableTypePerformance.'"}'),
|
||||
amis()->NumberControl()
|
||||
->name('task_performance[expected_performance]')
|
||||
->label(__('plan.task_performance.expected_performance'))
|
||||
->placeholder(__('plan.task_performance.expected_performance'))
|
||||
->precision(2)
|
||||
->showSteps(false)
|
||||
->required()
|
||||
->visibleOn('${planable_type == "'.$planableTypePerformance.'"}'),
|
||||
// 清洁卫生
|
||||
amis()->SelectControl('task_hygiene[store_id]', __('plan.task_hygiene.store'))
|
||||
->source(admin_url('api/stores'))
|
||||
->labelField('title')
|
||||
->valueField('id')
|
||||
->clearable()
|
||||
->required()
|
||||
->visibleOn('${planable_type == "'.$planableTypeHygiene.'"}'),
|
||||
])
|
||||
->reload('task-performance-table,task-hygiene-table');
|
||||
|
||||
return DrawerAction::make()
|
||||
->label(__('admin.create'))
|
||||
->icon('fa fa-add')
|
||||
->level('primary')
|
||||
->drawer(
|
||||
Drawer::make()
|
||||
->title(__('admin.create'))
|
||||
->size('lg')
|
||||
->closeOnOutside()
|
||||
->body($form)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务 - 行编辑按钮
|
||||
*/
|
||||
protected function taskRowEditButton(): DrawerAction
|
||||
{
|
||||
$planableTypePerformance = (new PlanPerformance())->getMorphClass();
|
||||
|
||||
$form = amis()->Form()
|
||||
->title('')
|
||||
->api('put:'.admin_url('/plan/tasks/${id}'))
|
||||
->redirect('')
|
||||
->body([
|
||||
amis()->HiddenControl('id'),
|
||||
// 任务指标
|
||||
amis()->NumberControl()
|
||||
->name('task_performance[expected_performance]')
|
||||
->label(__('plan.task_performance.expected_performance'))
|
||||
->value('${taskable.expected_performance}')
|
||||
->precision(2)
|
||||
->showSteps(false)
|
||||
->required()
|
||||
->visibleOn('${planable_type == "'.$planableTypePerformance.'"}'),
|
||||
])
|
||||
->reload('task-performance-table');
|
||||
|
||||
return DrawerAction::make()
|
||||
->label(__('admin.edit'))
|
||||
->icon('fa-regular fa-pen-to-square')
|
||||
->level('link')
|
||||
->drawer(
|
||||
Drawer::make()
|
||||
->title(__('admin.edit'))
|
||||
->size('lg')
|
||||
->closeOnOutside()
|
||||
->body($form)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务 - 行删除按钮
|
||||
*/
|
||||
protected function taskRowDeleteButton(): AjaxAction
|
||||
{
|
||||
return amis()->AjaxAction()
|
||||
->label(__('admin.delete'))
|
||||
->icon('fa-regular fa-trash-can')
|
||||
->level('link')
|
||||
->confirmText(__('admin.confirm_delete'))
|
||||
->api('delete:'.admin_url('/plan/tasks/${id}'));
|
||||
}
|
||||
|
||||
protected function planableTypeOptions(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -245,6 +245,8 @@ class PlanService extends BaseService
|
|||
if ($plans->contains(fn (Plan $plan) => $plan->isPublished())) {
|
||||
admin_abort('不能删除已发布的任务计划');
|
||||
}
|
||||
|
||||
$plans->each(fn (Plan $plan) => $plan->planable()->delete());
|
||||
}
|
||||
|
||||
public function addRelations($query, string $scene = 'list')
|
||||
|
|
|
|||
|
|
@ -4,8 +4,18 @@ namespace App\Admin\Services\Plan;
|
|||
|
||||
use App\Admin\Filters\TaskFilter;
|
||||
use App\Admin\Services\BaseService;
|
||||
use App\Enums\TaskStatus;
|
||||
use App\Models\Ledger;
|
||||
use App\Models\Plan;
|
||||
use App\Models\PlanHygiene;
|
||||
use App\Models\PlanPerformance;
|
||||
use App\Models\Store;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskHygiene;
|
||||
use App\Models\TaskPerformance;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
/**
|
||||
* @method Task getModel()
|
||||
|
|
@ -16,4 +26,174 @@ class TaskService extends BaseService
|
|||
protected string $modelName = Task::class;
|
||||
|
||||
protected string $modelFilterName = TaskFilter::class;
|
||||
|
||||
public function store($data): bool
|
||||
{
|
||||
if (! isset($data['plan_id'])) {
|
||||
admin_abort('任务计划未找到');
|
||||
}
|
||||
|
||||
$plan = Plan::findOrFail($data['plan_id']);
|
||||
|
||||
switch (get_class($planable = $plan->planable)) {
|
||||
case PlanPerformance::class:
|
||||
$payload = $data['task_performance'] ?? [];
|
||||
|
||||
Validator::validate(
|
||||
data: $data,
|
||||
rules: [
|
||||
'store_id' => ['bail', 'required'],
|
||||
'expected_performance' => ['bail', 'required', 'numeric', 'min:0'],
|
||||
],
|
||||
attributes: [
|
||||
'store_id' => __('plan.task_performance.store_id'),
|
||||
'expected_performance' => __('plan.task_performance.expected_performance'),
|
||||
],
|
||||
);
|
||||
|
||||
/** @var \App\Models\Store */
|
||||
$store = Store::findOrFail($payload['store_id']);
|
||||
|
||||
if (
|
||||
TaskPerformance::where('store_id', $store->id)
|
||||
->where('month', $planable->month)
|
||||
->exists()
|
||||
) {
|
||||
admin_abort('门店已有业绩指标任务');
|
||||
}
|
||||
|
||||
// 月份
|
||||
$month = Carbon::createFromFormat('Y-m', $planable->month);
|
||||
// 开始时间
|
||||
$startAt = $month->copy()->startOfMonth();
|
||||
// 结束时间
|
||||
$endAt = $month->copy()->endOfMonth();
|
||||
|
||||
// 门店实际业绩
|
||||
$actualPerformance = Ledger::where('store_id', $store->id)
|
||||
->whereBetween('date', [$startAt->format('Y-m-d'), $endAt->format('Y-m-d')])
|
||||
->sum('sales');
|
||||
|
||||
/** @var \App\Models\TaskPerformance */
|
||||
$taskable = TaskPerformance::create([
|
||||
'month' => $planable->month,
|
||||
'store_id' => $store->id,
|
||||
'store_master_id' => $store->master_id,
|
||||
'expected_performance' => $payload['expected_performance'],
|
||||
'actual_performance' => $actualPerformance,
|
||||
]);
|
||||
|
||||
// 月份
|
||||
$month = Carbon::createFromFormat('Y-m', $planable->month);
|
||||
|
||||
$taskable->task()->create([
|
||||
'plan_id' => $plan->id,
|
||||
'name' => '业绩指标',
|
||||
'start_at' => $month->copy()->startOfMonth(),
|
||||
'end_at' => $month->copy()->endOfMonth(),
|
||||
'task_status' => $taskable->isCompleted() ? TaskStatus::Success : TaskStatus::Pending,
|
||||
'completed_at' => $taskable->isCompleted() ? now() : null,
|
||||
]);
|
||||
break;
|
||||
|
||||
case PlanHygiene::class:
|
||||
$payload = $data['task_hygiene'] ?? [];
|
||||
|
||||
Validator::validate(
|
||||
data: $data,
|
||||
rules: [
|
||||
'store_id' => ['bail', 'required'],
|
||||
],
|
||||
attributes: [
|
||||
'store_id' => __('plan.task_hygiene.store_id'),
|
||||
],
|
||||
);
|
||||
|
||||
/** @var \App\Models\Store */
|
||||
$store = Store::findOrFail($payload['store_id']);
|
||||
|
||||
if (
|
||||
TaskHygiene::where('store_id', $store->id)
|
||||
->where('month', $planable->month)
|
||||
->exists()
|
||||
) {
|
||||
admin_abort('门店已有清洁卫生任务');
|
||||
}
|
||||
|
||||
$taskable = TaskHygiene::create([
|
||||
'month' => $planable->month,
|
||||
'store_id' => $store->id,
|
||||
'store_master_id' => $store->master_id,
|
||||
]);
|
||||
|
||||
// 月份
|
||||
$month = Carbon::createFromFormat('Y-m', $planable->month);
|
||||
|
||||
$taskable->task()->create([
|
||||
'plan_id' => $plan->id,
|
||||
'name' => '清洁卫生',
|
||||
'start_at' => $month->copy()->startOfMonth(),
|
||||
'end_at' => $month->copy()->endOfMonth(),
|
||||
'task_status' => TaskStatus::Pending,
|
||||
]);
|
||||
break;
|
||||
|
||||
default:
|
||||
admin_abort('任务计划不可新增任务');
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function update($primaryKey, $data): bool
|
||||
{
|
||||
$task = Task::findOrFail($primaryKey);
|
||||
|
||||
if (in_array($task->task_status, [TaskStatus::Success, TaskStatus::Failed])) {
|
||||
admin_abort("[{$task->task_status->text()}]任务不可修改");
|
||||
}
|
||||
|
||||
switch (get_class($taskable = $task->taskable)) {
|
||||
case TaskPerformance::class:
|
||||
$payload = $data['task_performance'] ?? [];
|
||||
|
||||
Validator::validate(
|
||||
data: $payload,
|
||||
rules: [
|
||||
'expected_performance' => ['bail', 'required', 'numeric', 'min:0'],
|
||||
],
|
||||
attributes: [
|
||||
'expected_performance' => __('plan.task_performance.expected_performance'),
|
||||
],
|
||||
);
|
||||
|
||||
$taskable->update([
|
||||
'expected_performance' => $payload['expected_performance'],
|
||||
]);
|
||||
|
||||
if ($taskable->isCompleted()) {
|
||||
$task->update([
|
||||
'task_status' => TaskStatus::Success,
|
||||
'completed_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
admin_abort('任务不可修改');
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function preDelete(array $ids): void
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$tasks = Task::findMany($ids);
|
||||
|
||||
$tasks->each(fn (Task $task) => $task->taskable()->delete());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,6 +154,9 @@ Route::group([
|
|||
// 任务计划
|
||||
$router->resource('plans', PlanController::class);
|
||||
$router->post('/plans/{plan}/publish', [PlanController::class, 'publish'])->name('plans.publish');
|
||||
$router->post('/tasks', [TaskController::class, 'store'])->name('plans.task_create');
|
||||
$router->put('/tasks/{task}', [TaskController::class, 'update'])->name('plans.task_update');
|
||||
$router->delete('/tasks/{task}', [TaskController::class, 'destroy'])->name('plans.task_delete');
|
||||
});
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ class Plan extends Model
|
|||
'planable_type',
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::deleting(function (Task $model) {
|
||||
$model->planable()->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function planable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ class Task extends Model
|
|||
'completed_at',
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::deleting(function (Task $model) {
|
||||
$model->taskable()->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function taskable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
|
|
|
|||
|
|
@ -34,4 +34,12 @@ class TaskPerformance extends Model
|
|||
{
|
||||
return $this->belongsTo(Employee::class, 'store_master_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 此业绩指标是否已完成
|
||||
*/
|
||||
public function isCompleted(): bool
|
||||
{
|
||||
return bccomp($this->actual_performance, $this->expected_performance, 2) >= 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,7 +192,11 @@ class AdminPermissionSeeder extends Seeder
|
|||
'icon' => 'tdesign:task',
|
||||
'uri' => '/plan/plans',
|
||||
'resource' => true,
|
||||
'children' => [],
|
||||
'children' => [
|
||||
'task_create' => '创建任务',
|
||||
'task_update' => '编辑任务',
|
||||
'task_delete' => '删除任务',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ return [
|
|||
'expected_performance' => '目标业绩',
|
||||
],
|
||||
|
||||
'plan_hygiene' => [
|
||||
'task_hygiene' => [
|
||||
'month' => '月份',
|
||||
'store' => '门店',
|
||||
'store_master' => '店长',
|
||||
|
|
|
|||
Loading…
Reference in New Issue