diff --git a/app/Admin/Controllers/Plan/PlanController.php b/app/Admin/Controllers/Plan/PlanController.php index f923508..02f10a4 100644 --- a/app/Admin/Controllers/Plan/PlanController.php +++ b/app/Admin/Controllers/Plan/PlanController.php @@ -5,6 +5,8 @@ namespace App\Admin\Controllers\Plan; use App\Admin\Controllers\AdminController; use App\Admin\Services\Plan\PlanService; use App\Enums\PlanStatus; +use App\Enums\TaskLedgerStatus; +use App\Enums\TaskPerformanceStatus; use App\Enums\TaskStatus; use App\Models\PlanHygiene; use App\Models\PlanLedger; @@ -198,12 +200,10 @@ 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('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')), - amis()->TableColumn('task_status', __('plan.task.status'))->type('mapping')->map(TaskStatus::labelMap()), + amis()->TableColumn('taskable.task_status', __('plan.task_ledger.status'))->type('mapping')->map(TaskLedgerStatus::labelMap()), amis()->TableColumn('completed_at', __('plan.task.completed_at')), amis()->TableColumn('created_at', __('plan.task.created_at')), ]) @@ -219,14 +219,12 @@ class PlanController extends AdminController 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('taskable.task_status', __('plan.task_performance.status'))->type('mapping')->map(TaskPerformanceStatus::labelMap()), amis()->TableColumn('completed_at', __('plan.task.completed_at')), amis()->TableColumn('created_at', __('plan.task.created_at')), $this->rowActions([ diff --git a/app/Admin/Controllers/Plan/TaskController.php b/app/Admin/Controllers/Plan/TaskController.php index 17d74fe..d089015 100644 --- a/app/Admin/Controllers/Plan/TaskController.php +++ b/app/Admin/Controllers/Plan/TaskController.php @@ -34,6 +34,10 @@ class TaskController extends AdminController ->latest('id') ->get(); - return $this->response()->success($tasks); + return $this->response()->success( + $tasks->map(function (Task $task) { + return tap($task, fn (Task $task) => $task->taskable?->setRelation('task', $task->withoutRelations())); + }) + ); } } diff --git a/app/Admin/Services/Plan/PlanService.php b/app/Admin/Services/Plan/PlanService.php index a7dcfcb..d95bb5e 100644 --- a/app/Admin/Services/Plan/PlanService.php +++ b/app/Admin/Services/Plan/PlanService.php @@ -16,6 +16,7 @@ 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\Validator; use Illuminate\Validation\Rule; @@ -32,56 +33,81 @@ class PlanService extends BaseService public function store($data): bool { - $planableTypePerformance = (new PlanPerformance())->getMorphClass(); - $planableTypeHygiene = (new PlanHygiene())->getMorphClass(); - - $rules = array_merge( - [ - 'name' => ['bail', 'required', 'max:255'], - 'planable_type' => ['bail', 'required', Rule::in([$planableTypePerformance, $planableTypeHygiene])], - ], - match ($data['planable_type'] ?? '') { - $planableTypePerformance => [ - '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'], - ], - $planableTypeHygiene => [ - 'plan_hygiene.month' => ['bail', 'required', 'date_format:Y-m'], - ], - }, - ); - Validator::validate( data: $data, - rules: $rules, + 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'), - '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'), ], ); - switch ($data['planable_type']) { - case $planableTypePerformance: - $planable = PlanPerformance::create($data['plan_performance']); + $planableType = Relation::getMorphedModel($data['planable_type']); + + switch ($planableType) { + // 清洁卫生 + 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 = PlanPerformance::create($payload); + + $planable->plan()->create([ + 'name' => $data['name'], + 'plan_status' => PlanStatus::Pending, + ]); + break; - case $planableTypeHygiene: - $planable = PlanHygiene::create($data['plan_hygiene']); + // 业绩指标 + 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; } - $planable->plan()->create([ - 'name' => $data['name'], - 'plan_status' => PlanStatus::Pending, - ]); - return true; } @@ -94,20 +120,21 @@ class PlanService extends BaseService admin_abort('任务计划已发布'); } + $planableType = Relation::getMorphedModel($plan->planable_type); + $rules = array_merge( - [ - 'name' => ['filled', 'max:255'], - ], - match (get_class($plan->planable)) { + ['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'], ], - PlanHygiene::class => [ - 'plan_hygiene.month' => ['bail', 'required', 'date_format:Y-m'], - ], }, ); @@ -129,7 +156,7 @@ class PlanService extends BaseService } $plan->save(); - switch (get_class($plan->planable)) { + switch ($planableType) { case PlanPerformance::class: $plan->planable->update($data['plan_performance']); break; @@ -151,22 +178,20 @@ class PlanService extends BaseService admin_abort('任务计划已发布'); } - switch (get_class($planable = $plan->planable)) { + switch (Relation::getMorphedModel($plan->planable_type)) { // 清洁卫生 case PlanHygiene::class: - // 任务计划月份 - $month = Carbon::createFromFormat('Y-m', $planable->month); // 任务开始时间 - $startAt = $month->copy()->startOfMonth(); + $startAt = Carbon::createFromFormat('Y-m-d H:i:s', "{$plan->planable->month}-01 00:00:00"); // 任务结束时间 - $endAt = $month->copy()->endOfMonth(); + $endAt = $startAt->copy()->endOfMonth(); /** @var \Illuminate\Database\Eloquent\Collection */ - $stores = Store::findMany($planable->store_ids); + $stores = Store::findMany($plan->planable->store_ids); foreach ($stores as $store) { $taskable = TaskHygiene::create([ - 'month' => $planable->month, + 'month' => $plan->planable->month, 'store_id' => $store->id, 'store_master_id' => $store->master_id, ]); @@ -182,16 +207,14 @@ class PlanService extends BaseService // 业绩指标 case PlanPerformance::class: - // 任务计划月份 - $month = Carbon::createFromFormat('Y-m', $planable->month); // 任务开始时间 - $startAt = $month->copy()->startOfMonth(); + $startAt = Carbon::createFromFormat('Y-m-d H:i:s', "{$plan->planable->month}-01 00:00:00"); // 任务结束时间 - $endAt = $month->copy()->endOfMonth(); + $endAt = $startAt->copy()->endOfMonth(); $input = [ - 'category_id' => $planable->store_category_id, - 'level_id' => $planable->store_level_id, + 'category_id' => $plan->planable->store_category_id, + 'level_id' => $plan->planable->store_level_id, ]; /** @var \Illuminate\Database\Eloquent\Collection */ @@ -199,10 +222,10 @@ class PlanService extends BaseService foreach ($stores as $store) { $taskable = TaskPerformance::create([ - 'month' => $planable->month, + 'month' => $plan->planable->month, 'store_id' => $store->id, 'store_master_id' => $store->master_id, - 'expected_performance' => $planable->performance, + 'expected_performance' => $plan->planable->performance, 'actual_performance' => 0, ]); $taskable->task()->create([ diff --git a/app/Enums/TaskLedgerStatus.php b/app/Enums/TaskLedgerStatus.php new file mode 100644 index 0000000..3bc9abe --- /dev/null +++ b/app/Enums/TaskLedgerStatus.php @@ -0,0 +1,36 @@ +value]; + } + + public static function options(): array + { + return [ + self::None->value => '未开始', + self::Pending->value => '待完成', + self::Success->value => '已完成', + self::Failed->value => '未完成', + ]; + } + + public static function labelMap(): array + { + return [ + self::None->value => ''.self::None->text().'', + self::Pending->value => ''.self::Pending->text().'', + self::Success->value => ''.self::Success->text().'', + self::Failed->value => ''.self::Failed->text().'', + ]; + } +} diff --git a/app/Enums/TaskPerformanceStatus.php b/app/Enums/TaskPerformanceStatus.php index 232ad60..ca6b218 100644 --- a/app/Enums/TaskPerformanceStatus.php +++ b/app/Enums/TaskPerformanceStatus.php @@ -8,4 +8,29 @@ enum TaskPerformanceStatus: int case Pending = 2; // 待完成 case Success = 3; // 已完成 case Failed = 4; // 未完成 + + public function text(): string + { + return self::options()[$this->value]; + } + + public static function options(): array + { + return [ + self::None->value => '未开始', + self::Pending->value => '待完成', + self::Success->value => '已完成', + self::Failed->value => '未完成', + ]; + } + + public static function labelMap(): array + { + return [ + self::None->value => ''.self::None->text().'', + self::Pending->value => ''.self::Pending->text().'', + self::Success->value => ''.self::Success->text().'', + self::Failed->value => ''.self::Failed->text().'', + ]; + } } diff --git a/app/Http/Controllers/Api/Account/TaskPerformanceController.php b/app/Http/Controllers/Api/Account/TaskPerformanceController.php index baa38b8..e114c69 100644 --- a/app/Http/Controllers/Api/Account/TaskPerformanceController.php +++ b/app/Http/Controllers/Api/Account/TaskPerformanceController.php @@ -3,10 +3,9 @@ namespace App\Http\Controllers\Api\Account; use App\Http\Controllers\Api\Controller; +use App\Http\Resources\TaskPerformanceResource; use App\Models\TaskPerformance; use Illuminate\Http\Request; -use Illuminate\Support\Carbon; -use Illuminate\Support\Collection; class TaskPerformanceController extends Controller { @@ -16,7 +15,8 @@ class TaskPerformanceController extends Controller $user = $request->user(); /** @var \Illuminate\Database\Eloquent\Collection */ - $taskPerformances = TaskPerformance::where('store_id', $user->store_id) + $taskPerformances = TaskPerformance::with(['task']) + ->where('store_id', $user->store_id) ->when($request->input('filter'), function ($query, $filter) { $now = now(); switch ($filter) { @@ -30,15 +30,7 @@ class TaskPerformanceController extends Controller }) ->get(); - return $taskPerformances->map(function (TaskPerformance $taskable) { - return [ - 'id' => $taskable->id, - 'month' => $taskable->month, - 'actual_performance' => trim_zeros($taskable->actual_performance), - 'expected_performance' => trim_zeros($taskable->expected_performance), - 'status' => $taskable->task_status, - ]; - }); + return TaskPerformanceResource::collection($taskPerformances); // return $taskPerformances->groupBy(fn (TaskPerformance $taskable) => Carbon::createFromFormat('Y-m', $taskable->month)->year) // ->map(function (Collection $collection) { diff --git a/app/Http/Controllers/Api/TaskController.php b/app/Http/Controllers/Api/TaskController.php new file mode 100644 index 0000000..46a20e6 --- /dev/null +++ b/app/Http/Controllers/Api/TaskController.php @@ -0,0 +1,52 @@ +user(); + + $orderBy = <<<'MySQL' + CASE + WHEN task_status = 1 THEN 100 + ELSE 0 + END +MySQL; + + /** @var \Illuminate\Database\Eloquent\Collection */ + $tasks = Task::with(['taskable']) + ->whereHasMorph( + 'taskable', + [TaskHygiene::class, TaskLedger::class], + function (Builder $query, string $type) use ($user) { + switch ($type) { + case TaskLedger::class: + case TaskHygiene::class: + if ($user->isStoreMaster()) { + $query->where('store_id', $user->store_id); + } else { + $query->whereRaw('1!=1'); + } + break; + } + } + ) + ->orderBy(DB::raw($orderBy), 'DESC') + ->orderBy('start_at', 'ASC') + ->orderBy('end_at', 'ASC') + ->simplePaginate($request->query('per_page', 20)); + + return TaskResource::collection($tasks); + } +} diff --git a/app/Http/Resources/TaskHygieneResource.php b/app/Http/Resources/TaskHygieneResource.php new file mode 100644 index 0000000..dd69d58 --- /dev/null +++ b/app/Http/Resources/TaskHygieneResource.php @@ -0,0 +1,26 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->resource->id, + 'month' => $this->resource->month, + 'store' => StoreResource::make($this->whenLoaded('store')), + 'description' => $this->resource->description, + 'photos' => $this->resource->photos, + 'created_at' => $this->resource->created_at->timestamp, + ]; + } +} diff --git a/app/Http/Resources/TaskLedgerResource.php b/app/Http/Resources/TaskLedgerResource.php new file mode 100644 index 0000000..2feb9dc --- /dev/null +++ b/app/Http/Resources/TaskLedgerResource.php @@ -0,0 +1,25 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->resource->id, + 'date' => $this->resource->date, + 'store' => StoreResource::make($this->whenLoaded('store')), + 'status' => $this->resource->task_status, + 'created_at' => $this->resource->created_at->timestamp, + ]; + } +} diff --git a/app/Http/Resources/TaskPerformanceResource.php b/app/Http/Resources/TaskPerformanceResource.php new file mode 100644 index 0000000..2808fed --- /dev/null +++ b/app/Http/Resources/TaskPerformanceResource.php @@ -0,0 +1,27 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->resource->id, + 'month' => $this->resource->month, + 'store' => StoreResource::make($this->whenLoaded('store')), + 'actual_performance' => trim_zeros($this->resource->actual_performance), + 'expected_performance' => trim_zeros($this->resource->expected_performance), + 'status' => $this->resource->task_status, + 'created_at' => $this->resource->created_at->timestamp, + ]; + } +} diff --git a/app/Http/Resources/TaskResource.php b/app/Http/Resources/TaskResource.php new file mode 100644 index 0000000..8e86343 --- /dev/null +++ b/app/Http/Resources/TaskResource.php @@ -0,0 +1,47 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->resource->id, + 'name' => $this->resource->name, + 'taskable_type' => $this->resource->taskable_type, + 'taskable_id' => $this->resource->taskable_id, + 'taskable' => $this->when($this->resource->relationLoaded('taskable'), function () { + $taskable = $this->resource->taskable; + + switch (Relation::getMorphedModel($this->resource->taskable_type)) { + case TaskLedger::class: + return TaskLedgerResource::make($taskable); + + case TaskHygiene::class: + return TaskHygieneResource::make($taskable); + + case TaskPerformance::class: + return TaskPerformanceResource::make($taskable); + } + + return $taskable; + }), + 'start_at' => (int) $this->resource->start_at?->timestamp, + 'end_at' => (int) $this->resource->end_at?->timestamp, + 'created_at' => $this->resource->created_at->timestamp, + ]; + } +} diff --git a/app/Models/TaskLedger.php b/app/Models/TaskLedger.php index 2be71f8..4fcc565 100644 --- a/app/Models/TaskLedger.php +++ b/app/Models/TaskLedger.php @@ -2,7 +2,9 @@ namespace App\Models; +use App\Enums\TaskLedgerStatus; 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; @@ -12,6 +14,10 @@ class TaskLedger extends Model { use HasFactory, HasDateTimeFormatter; + protected $appends = [ + 'task_status', + ]; + protected $fillable = [ 'date', 'store_id', @@ -32,4 +38,30 @@ class TaskLedger extends Model { return $this->belongsTo(Employee::class, 'store_master_id'); } + + /** + * 任务状态 + */ + protected function taskStatus(): Attribute + { + return Attribute::make( + get: function (mixed $value, array $attributes) { + /** @var \App\Models\Task */ + $task = $this->task; + + if ($task->isSuccess()) { + return TaskLedgerStatus::Success; + } + + $datetime = now(); + + if ($datetime->lt($task->start_at)) { + return TaskLedgerStatus::None; + } elseif ($datetime->gte($task->end_at)) { + return TaskLedgerStatus::Failed; + } + return TaskLedgerStatus::Pending; + }, + ); + } } diff --git a/app/Models/TaskPerformance.php b/app/Models/TaskPerformance.php index 3a300ce..18bca7c 100644 --- a/app/Models/TaskPerformance.php +++ b/app/Models/TaskPerformance.php @@ -9,12 +9,15 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphOne; -use Illuminate\Support\Carbon; class TaskPerformance extends Model { use HasFactory, HasDateTimeFormatter; + protected $appends = [ + 'task_status', + ]; + protected $fillable = [ 'month', 'store_id', diff --git a/lang/zh_CN/plan.php b/lang/zh_CN/plan.php index fc5eee2..8fc94ab 100644 --- a/lang/zh_CN/plan.php +++ b/lang/zh_CN/plan.php @@ -39,6 +39,7 @@ return [ 'date' => '日期', 'store' => '门店', 'store_master' => '店长', + 'status' => '状态', ], 'task_performance' => [ @@ -47,11 +48,13 @@ return [ 'store_master' => '店长', 'actual_performance' => '实际业绩', 'expected_performance' => '目标业绩', + 'status' => '状态', ], 'task_hygiene' => [ 'month' => '月份', 'store' => '门店', 'store_master' => '店长', + 'status' => '状态', ], ]; diff --git a/routes/api.php b/routes/api.php index 5ce4df4..938c6cf 100644 --- a/routes/api.php +++ b/routes/api.php @@ -11,6 +11,7 @@ use App\Http\Controllers\Api\KeywordController; use App\Http\Controllers\Api\LedgerController; use App\Http\Controllers\Api\ReimbursementController; use App\Http\Controllers\Api\StatisticsController; +use App\Http\Controllers\Api\TaskController; use Illuminate\Support\Facades\Route; Route::post('/auth/login', [AccessTokenController::class, 'store']); @@ -54,6 +55,9 @@ Route::group([ // 数据上报 Route::apiResource('/ledgers', LedgerController::class)->only(['store', 'show']); + // 任务列表 + Route::get('tasks', [TaskController::class, 'index']); + // 举报投诉 Route::post('complaints', [ComplaintController::class, 'store']); // 意见箱