store-manage/app/Admin/Services/Plan/PlanService.php

313 lines
11 KiB
PHP

<?php
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\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\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
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
{
Validator::validate(
data: $data,
rules: [
'name' => ['bail', 'required', 'max:255'],
'planable_type' => [
'bail',
'required',
Rule::in([
(new PlanHygiene())->getMorphClass(),
(new PlanPerformance())->getMorphClass(),
]),
],
],
attributes: [
'name' => __('plan.plan.name'),
'planable_type' => __('plan.plan.type'),
],
);
switch (Relation::getMorphedModel($data['planable_type'])) {
// 清洁卫生
case PlanHygiene::class:
$payload = $data['plan_hygiene'] ?? [];
Validator::validate(
data: $payload,
rules: [
'month' => ['bail', 'required', 'date_format:Y-m'],
],
attributes: [
'month' => __('plan.plan_hygiene.month'),
],
);
$planable = PlanHygiene::create($payload);
$planable->plan()->create([
'name' => $data['name'],
'plan_status' => PlanStatus::Pending,
]);
break;
// 业绩指标
case PlanPerformance::class:
$payload = $data['plan_performance'] ?? [];
Validator::validate(
data: $payload,
rules: [
'month' => ['bail', 'required', 'date_format:Y-m'],
'store_category_id' => ['bail', 'required', Rule::exists(Keyword::class, 'key')],
'store_level_id' => ['bail', 'nullable', Rule::exists(Keyword::class, 'key')],
'performance' => ['bail', 'required', 'numeric', 'min:0'],
],
attributes: [
'month' => __('plan.plan_performance.month'),
'store_category_id' => __('plan.plan_performance.store_category_id'),
'store_level_id' => __('plan.plan_performance.store_level_id'),
'performance' => __('plan.plan_performance.performance'),
],
);
$planable = PlanPerformance::create($payload);
$planable->plan()->create([
'name' => $data['name'],
'plan_status' => PlanStatus::Pending,
]);
break;
}
return true;
}
public function update($primaryKey, $data): bool
{
/** @var \App\Models\Plan|null */
$plan = Plan::findOrFail($primaryKey);
if ($plan->isPublished()) {
admin_abort('任务计划已发布');
}
$planableType = Relation::getMorphedModel($plan->planable_type);
$rules = array_merge(
['name' => ['filled', 'max:255']],
match ($planableType) {
PlanHygiene::class => [
'plan_hygiene.month' => ['bail', 'required', 'date_format:Y-m'],
],
PlanPerformance::class => [
'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'],
],
},
);
Validator::validate(
data: $data,
rules: $rules,
attributes: [
'name' => __('plan.plan.name'),
'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'),
],
);
if (array_key_exists('name', $data)) {
$plan->name = $data['name'];
}
$plan->save();
switch ($planableType) {
case PlanPerformance::class:
$plan->planable->update($data['plan_performance']);
break;
case PlanHygiene::class:
$plan->planable->update($data['plan_hygiene']);
break;
}
return true;
}
public function publish($id)
{
/** @var \App\Models\Plan */
$plan = Plan::findOrFail($id);
if ($plan->isPublished()) {
admin_abort('任务计划已发布');
}
switch (Relation::getMorphedModel($plan->planable_type)) {
// 清洁卫生
case PlanHygiene::class:
// 任务开始时间
$startAt = Carbon::createFromFormat('Y-m-d H:i:s', "{$plan->planable->month}-01 00:00:00");
// 任务结束时间
$endAt = $startAt->copy()->endOfMonth();
/** @var \Illuminate\Database\Eloquent\Collection */
$stores = Store::findMany($plan->planable->store_ids);
foreach ($stores as $store) {
$taskable = TaskHygiene::create([
'month' => $plan->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:
// 任务开始时间
$startAt = Carbon::createFromFormat('Y-m-d H:i:s', "{$plan->planable->month}-01 00:00:00");
// 任务结束时间
$endAt = $startAt->copy()->endOfMonth();
// 门店分类IDs
$categoryIds = value(function ($id) {
$ids = collect();
if ($parent = Keyword::where('key', $id)->first()) {
/** @var \Illuminate\Support\Collection */
$ids = Keyword::where('path', 'like', "%-{$parent->id}-%")->pluck('key');
}
return $ids->push($id);
}, $plan->planable->store_category_id);
// 门店业绩统计
$aggregates = Ledger::select(['store_id', DB::raw('SUM(`sales`) as sales')])
->whereBetween('date', [$startAt->format('Y-m-d'), $endAt->format('Y-m-d')])
->groupBy('store_id')
->pluck('sales', 'store_id')
->toArray();
/** @var \Illuminate\Database\Eloquent\Collection */
$stores = Store::filter(['level_id' => $plan->planable->store_level_id], StoreFilter::class)
->whereIn('category_id', $categoryIds)
->get(['id', 'master_id']);
foreach ($stores as $store) {
$taskable = TaskPerformance::create([
'month' => $plan->planable->month,
'store_id' => $store->id,
'store_master_id' => $store->master_id,
'expected_performance' => $plan->planable->performance,
'actual_performance' => $aggregates[$store->id] ?? 0,
]);
$task = new Task([
'plan_id' => $plan->id,
'name' => '业绩指标',
'start_at' => $startAt,
'end_at' => $endAt,
'task_status' => TaskStatus::Pending,
]);
if ($taskable->isSuccess()) {
$task->task_status = TaskStatus::Success;
$task->completed_at = now();
}
$taskable->task()->save($task);
}
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 */
$plans = Plan::findMany($ids);
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')
{
if (in_array($scene, ['edit', 'detail'])) {
$query->with([
'planable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
PlanPerformance::class => ['storeCategory', 'storeLevel'],
]);
},
]);
}
}
}