任务计划

main
Jing Li 2024-04-14 23:41:07 +08:00
parent 6dec9bba36
commit 882f2bdb7c
17 changed files with 649 additions and 17 deletions

View File

@ -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' => '<span class="label label-primary">总账录入</span>',
'plan_performances' => '<span class="label label-danger">业绩指标</span>',
'plan_hygienes' => '<span class="label label-success">清洁卫生</span>',
];
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([
]);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Admin\Filters;
use EloquentFilter\ModelFilter;
class PlanFilter extends ModelFilter
{
public function name($name)
{
$this->whereLike('name', $name);
}
public function planableType($planableType)
{
$this->whereIn('planable_type', explode(',', $planableType));
}
public function planStatus($planStatus)
{
$this->whereIn('plan_status', explode(',', $planStatus));
}
}

View File

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

View File

@ -0,0 +1,151 @@
<?php
namespace App\Admin\Services\Plan;
use App\Admin\Filters\PlanFilter;
use App\Admin\Services\BaseService;
use App\Enums\PlanStatus;
use App\Models\Keyword;
use App\Models\Plan;
use App\Models\PlanHygiene;
use App\Models\PlanPerformance;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
/**
* @method Plan getModel()
* @method Plan|Builder query()
*/
class PlanService extends BaseService
{
protected string $modelName = Plan::class;
protected string $modelFilterName = PlanFilter::class;
public function store($data): bool
{
$rules = array_merge(
[
'name' => ['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']);
}
}
}

View File

@ -1,5 +1,6 @@
<?php
use App\Admin\Controllers\AgreementController;
use App\Admin\Controllers\BaseKeywordController;
use App\Admin\Controllers\Complaint\ComplaintController;
use App\Admin\Controllers\Complaint\FeedbackController;
@ -18,6 +19,7 @@ use App\Admin\Controllers\Hr\RestController;
use App\Admin\Controllers\Hr\SignController;
use App\Admin\Controllers\Hr\SignLogController;
use App\Admin\Controllers\Hr\SignRepairController;
use App\Admin\Controllers\Plan\PlanController;
use App\Admin\Controllers\Store\DeviceController;
use App\Admin\Controllers\Store\EmployeeController as StoreEmployeeController;
use App\Admin\Controllers\Store\StoreController;
@ -27,7 +29,6 @@ use App\Admin\Controllers\System\AdminRoleController;
use App\Admin\Controllers\System\AdminUserController;
use App\Admin\Controllers\System\KeywordController;
use App\Admin\Controllers\System\WorkflowController;
use App\Admin\Controllers\AgreementController;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;
@ -145,7 +146,13 @@ Route::group([
| 任务计划
|--------------------------------------------------------------------------
*/
$router->resource('plan/plans', \App\Admin\Controllers\Plan\PlanController::class);
$router->group([
'prefix' => 'plan',
'as' => 'plan.',
], function (Router $router) {
// 任务计划
$router->resource('plans', PlanController::class);
});
/*
|--------------------------------------------------------------------------

View File

@ -0,0 +1,30 @@
<?php
namespace App\Enums;
enum PlanStatus: int
{
case Pending = 1;
case Published = 5;
public function text(): string
{
return self::options()[$this->value];
}
public static function options(): array
{
return [
self::Pending->value => '待发布',
self::Published->value => '已发布',
];
}
public static function labelMap(): array
{
return [
self::Pending->value => '<span class="label label-primary">'.self::Pending->text().'</span>',
self::Published->value => '<span class="label label-success">'.self::Published->text().'</span>',
];
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use App\Enums\PlanStatus;
use App\Traits\HasDateTimeFormatter;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Plan extends Model
{
use Filterable, HasFactory, HasDateTimeFormatter;
protected $attributes = [
'plan_status' => 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;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class PlanHygiene extends Model
{
use HasFactory;
protected $fillable = [
'month',
];
public function plan(): MorphOne
{
return $this->morphOne(Plan::class, 'planable');
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Models;
use App\Traits\HasDateTimeFormatter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class PlanLedger extends Model
{
use HasFactory, HasDateTimeFormatter;
protected $casts = [
'date' => 'date',
];
protected $fillable = [
'date',
];
public function plan(): MorphOne
{
return $this->morphOne(Plan::class, 'planable');
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class PlanPerformance extends Model
{
use HasFactory;
protected $fillable = [
'month', 'store_category_id', 'store_level_id', 'performance',
];
public function plan(): MorphOne
{
return $this->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');
}
}

View File

@ -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,

View File

@ -0,0 +1,30 @@
<?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('plans', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,30 @@
<?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('plan_ledgers', function (Blueprint $table) {
$table->id();
$table->date('date')->comment('日期');
$table->timestamps();
$table->unique(['date']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('plan_ledgers');
}
};

View File

@ -0,0 +1,31 @@
<?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('plan_performances', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,28 @@
<?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('plan_hygienes', function (Blueprint $table) {
$table->id();
$table->string('month')->comment('月份: 2024-04');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('plan_hygienes');
}
};

View File

@ -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' => [],
],
],
],
/*
|--------------------------------------------------------------------------
| 投诉意见

View File

@ -0,0 +1,16 @@
<?php
return [
'plan' => [
'id' => 'ID',
'name' => '计划名称',
'type' => '计划类型',
'status' => '计划状态',
'created_at' => '创建时间',
'updated_at' => '更新时间',
'month' => '月份',
'store_category_id' => '门店分类',
'store_level_id' => '门店等级',
'performance' => '业绩',
],
];