From 86648e67fcd993c725bcde8086ba535a316d23c5 Mon Sep 17 00:00:00 2001 From: Jing Li Date: Sat, 13 Apr 2024 17:59:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=A5=E9=94=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Admin/Services/WorkFlowService.php | 14 +- app/Filters/ReimbursementFilter.php | 9 + .../Api/ReimbursementController.php | 158 ++++++++++++++++++ app/Http/Resources/ReimbursementResource.php | 28 ++++ app/Http/Resources/WorkflowCheckResource.php | 23 +++ app/Models/Employee.php | 8 + app/Models/Reimbursement.php | 17 ++ routes/api.php | 4 + 8 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 app/Filters/ReimbursementFilter.php create mode 100644 app/Http/Controllers/Api/ReimbursementController.php create mode 100644 app/Http/Resources/ReimbursementResource.php create mode 100644 app/Http/Resources/WorkflowCheckResource.php diff --git a/app/Admin/Services/WorkFlowService.php b/app/Admin/Services/WorkFlowService.php index 87d8961..dedbd6e 100644 --- a/app/Admin/Services/WorkFlowService.php +++ b/app/Admin/Services/WorkFlowService.php @@ -2,7 +2,6 @@ namespace App\Admin\Services; -use App\Contracts\Checkable; use App\Enums\CheckStatus; use App\Enums\CheckType; use App\Models\Employee; @@ -34,16 +33,16 @@ class WorkFlowService extends BaseService public function apply(WorkflowCheck $check, Employee $user) { if ($check->check_status === CheckStatus::Success->value) { - return $this->setError('已经审核通过'); + admin_abort('已经审核通过'); } if ($check->check_status === CheckStatus::Processing->value) { - return $this->setError('正在审核中'); + admin_abort('正在审核中'); } $workflow = Workflow::where('key', $check->key)->first(); // 没有配置审核流程, 直接通过 if (! $workflow || ! $workflow->config) { - $this->success(); + $this->success($check); return true; } @@ -76,8 +75,7 @@ class WorkFlowService extends BaseService $checkValue = $checkUser->id; $checkName = $checkUser->name; } else { - return $this->setError('未知的审核类型: '.$item['type']); - break; + admin_abort('未知的审核类型: '.$item['type']); } $check->logs()->create([ 'batch_id' => $batchId, @@ -142,10 +140,10 @@ class WorkFlowService extends BaseService public function check(Employee $user, WorkflowLog $log, $status, $options = []) { if ($log->check_status != CheckStatus::Processing) { - return $this->setError('不可操作, 等待前面的审核完成'); + admin_abort('不可操作, 等待前面的审核完成'); } if (! $this->authCheck($user, $log)) { - return $this->setError('没有权限'); + admin_abort('没有权限'); } $attributes = ['check_status' => $status ? CheckStatus::Success : CheckStatus::Fail]; $attributes['checked_at'] = data_get($options, 'time', now()); diff --git a/app/Filters/ReimbursementFilter.php b/app/Filters/ReimbursementFilter.php new file mode 100644 index 0000000..cea21e5 --- /dev/null +++ b/app/Filters/ReimbursementFilter.php @@ -0,0 +1,9 @@ +user(); + + $reimbursements = $user->reimbursements() + ->filter($request->input()) + ->latest('id') + ->simplePaginate($request->input('per_page', 20)); + + return ReimbursementResource::collection( + $reimbursements->loadMissing(['type', 'workflow']), + ); + } + + public function store(Request $request, WorkFlowService $workFlowService): ReimbursementResource + { + $validated = $request->validate( + rules: [ + 'reimbursement_type_id' => ['bail', 'required', Rule::exists(Keyword::class, 'key')], + 'expense' => ['bail', 'required', 'numeric', 'min:0'], + 'reason' => ['bail', 'required', 'max:255'], + 'photos' => ['bail', 'required', 'array'], + ], + attributes: [ + 'reimbursement_type_id' => '报销分类', + 'expense' => '报销金额', + 'reason' => '报销原因', + 'photos' => '报销凭证', + ], + ); + + /** @var \App\Models\Employee */ + $user = $request->user(); + + try { + DB::beginTransaction(); + + /** @var \App\Models\Reimbursement */ + $reimbursement = $user->reimbursements()->create($validated); + + $workFlowService->apply($reimbursement->workflow, $user); + + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + + throw tap($th, fn ($th) => report($th)); + } + + return ReimbursementResource::make( + $reimbursement->load(['type', 'workflow']), + ); + } + + public function show($id, Request $request): ReimbursementResource + { + /** @var \App\Models\Employee */ + $user = $request->user(); + + /** @var \App\Models\Reimbursement */ + $reimbursement = $user->reimbursements()->find($id); + + if (is_null($reimbursement)) { + throw new RuntimeException('报销记录未找到'); + } + + return ReimbursementResource::make( + $reimbursement->load(['type', 'workflow']), + ); + } + + public function update($id, Request $request, WorkFlowService $workFlowService): ReimbursementResource + { + $validated = $request->validate( + rules: [ + 'reimbursement_type_id' => ['bail', 'required', Rule::exists(Keyword::class, 'key')], + 'expense' => ['bail', 'required', 'numeric', 'min:0'], + 'reason' => ['bail', 'required', 'max:255'], + 'photos' => ['bail', 'required', 'array'], + ], + attributes: [ + 'reimbursement_type_id' => '报销分类', + 'expense' => '报销金额', + 'reason' => '报销原因', + 'photos' => '报销凭证', + ], + ); + + /** @var \App\Models\Employee */ + $user = $request->user(); + + /** @var \App\Models\Reimbursement */ + $reimbursement = $user->reimbursements()->find($id); + + if (is_null($reimbursement)) { + throw new RuntimeException('报销记录未找到'); + } + + if (! $reimbursement->canUpdate()) { + throw new RuntimeException('['.$reimbursement->workflow->check_status->text().']报销记录不可修改'); + } + + try { + DB::beginTransaction(); + + $reimbursement->update($validated); + + $workFlowService->apply($reimbursement->workflow, $user); + + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + + throw tap($th, fn ($th) => report($th)); + } + + return ReimbursementResource::make( + $reimbursement->load(['type', 'workflow']), + ); + } + + public function destroy($id, Request $request) + { + /** @var \App\Models\Employee */ + $user = $request->user(); + + /** @var \App\Models\Reimbursement */ + $reimbursement = $user->reimbursements()->find($id); + + if (is_null($reimbursement)) { + throw new RuntimeException('报销记录未找到'); + } + + if (! $reimbursement->canDelete()) { + throw new RuntimeException('['.$reimbursement->workflow->check_status->text().']报销记录不可删除'); + } + + $reimbursement->delete(); + + return response()->noContent(); + } +} diff --git a/app/Http/Resources/ReimbursementResource.php b/app/Http/Resources/ReimbursementResource.php new file mode 100644 index 0000000..481f615 --- /dev/null +++ b/app/Http/Resources/ReimbursementResource.php @@ -0,0 +1,28 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'type' => KeywordResource::make($this->whenLoaded('type')), + 'workflow_check' => WorkflowCheckResource::make($this->whenLoaded('workflow')), + 'expense' => $this->expense, + 'reason' => $this->reason, + 'photos' => $this->photos, + 'created_at' => $this->created_at?->getTimestamp(), + 'updated_at' => $this->updated_at?->getTimestamp(), + ]; + } +} diff --git a/app/Http/Resources/WorkflowCheckResource.php b/app/Http/Resources/WorkflowCheckResource.php new file mode 100644 index 0000000..da6578e --- /dev/null +++ b/app/Http/Resources/WorkflowCheckResource.php @@ -0,0 +1,23 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'check_status' => $this->check_status, + 'checked_at' => $this->checked_at?->getTimestamp(), + 'check_remarks' => (string) $this->check_remarks, + ]; + } +} diff --git a/app/Models/Employee.php b/app/Models/Employee.php index 623394d..22a9b64 100644 --- a/app/Models/Employee.php +++ b/app/Models/Employee.php @@ -84,6 +84,14 @@ class Employee extends Model implements AuthenticatableContract return $this->hasMany(StoreMasterCommission::class, 'store_master_id'); } + /** + * 报销 + */ + public function reimbursements() + { + return $this->hasMany(Reimbursement::class); + } + // 管理的门店(店长) // public function masterStore() // { diff --git a/app/Models/Reimbursement.php b/app/Models/Reimbursement.php index abe9714..9c048e4 100644 --- a/app/Models/Reimbursement.php +++ b/app/Models/Reimbursement.php @@ -2,6 +2,8 @@ namespace App\Models; +use App\Enums\CheckStatus; +use App\Filters\ReimbursementFilter; use App\Traits\HasCheckable; use App\Traits\HasDateTimeFormatter; use EloquentFilter\Filterable; @@ -32,6 +34,21 @@ class Reimbursement extends Model return $this->belongsTo(Keyword::class, 'reimbursement_type_id', 'key'); } + public function modelFilter(): string + { + return ReimbursementFilter::class; + } + + public function canUpdate(): bool + { + return in_array($this->workflow?->check_status, [CheckStatus::None, CheckStatus::Fail, CheckStatus::Cancel]); + } + + public function canDelete(): bool + { + return in_array($this->workflow?->check_status, [CheckStatus::None, CheckStatus::Fail, CheckStatus::Cancel]); + } + protected function photos(): Attribute { return Attribute::make( diff --git a/routes/api.php b/routes/api.php index deae90a..9d57a19 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Api\FeedbackController; use App\Http\Controllers\Api\KeywordController; use App\Http\Controllers\Api\Ledger\LedgerController; use App\Http\Controllers\Api\Ledger\LotteryTypeController; +use App\Http\Controllers\Api\ReimbursementController; use App\Http\Controllers\Api\StatsController; use Illuminate\Support\Facades\Route; @@ -36,6 +37,9 @@ Route::group([ Route::get('/ledger/ledgers/{date}', [LedgerController::class, 'show']); Route::get('/ledger/lottery-types', [LotteryTypeController::class, 'index']); + // 报销管理 + Route::apiResource('reimbursements', ReimbursementController::class); + // 举报投诉 Route::post('complaints', [ComplaintController::class, 'store']); // 意见箱