发布任务计划

main
Jing Li 2024-04-17 20:50:05 +08:00
parent 95723d58f2
commit 9dbb4b0af7
12 changed files with 406 additions and 22 deletions

View File

@ -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('<span class="label label-primary m-l-sm">${title}</span>')
),
'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'));
}
}

View File

@ -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'],
]);
},
])

View File

@ -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'],
]);
},
]);
}
}
}

View File

@ -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');
});
/*

View File

@ -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');
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Models;
use App\Traits\HasCheckable;
use App\Traits\HasDateTimeFormatter;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class TaskHygiene extends Model
{
use HasCheckable, HasFactory, HasDateTimeFormatter;
protected $fillable = [
'month',
'store_id',
'store_master_id',
'description',
'photos',
];
public function task(): MorphOne
{
return $this->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 : []),
);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Models;
use App\Traits\HasDateTimeFormatter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class TaskPerformance extends Model
{
use HasFactory, HasDateTimeFormatter;
protected $fillable = [
'month',
'store_id',
'store_master_id',
'expected_performance',
'actual_performance',
];
public function task(): MorphOne
{
return $this->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');
}
}

View File

@ -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()

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('task_performances', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('task_hygienes', function (Blueprint $table) {
$table->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');
}
};

View File

@ -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' => '店长',
],
];

View File

@ -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']);