Browse Source

Initiate approvals, edits, deletions, etc.

moell 8 months ago
parent
commit
3498d453fe

+ 76 - 0
app/Http/Controllers/API/ApprovalController.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace App\Http\Controllers\API;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\API\Approval\CreateOrUpdateRequest;
+use App\Models\Approval;
+use App\Models\ApprovalFlow;
+use App\Models\Enums\ApprovalFlowObjectType;
+use App\Models\Enums\ApprovalFlowType;
+use App\Models\Enums\ApprovalObjectType;
+use App\Models\Enums\ObjectApprovalStatus;
+use App\Services\Approval\StoreService;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+
+class ApprovalController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     */
+    public function index()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     */
+    public function store(CreateOrUpdateRequest $request, StoreService $service)
+    {
+        $service->store();
+
+        return $this->created();
+    }
+
+    /**
+     * Display the specified resource.
+     */
+    public function show(string $id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     */
+    public function update(CreateOrUpdateRequest $request, string $id)
+    {
+        $approval = Approval::query()->findOrFail($id);
+
+        ApprovalObjectType::from($approval->object_type)->modelBuilderAllowed($approval->object_id)->findOrFail($approval->object_id);
+
+        $approval->remark = $request->remark;
+        $approval->save();
+
+        return $this->noContent();
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     */
+    public function destroy(string $id)
+    {
+        $approval = Approval::query()->findOrFail($id);
+
+        $object = ApprovalObjectType::from($approval->object_type)->modelBuilderAllowed($approval->object_id)->findOrFail($approval->object_id);
+
+        $approval->delete();
+
+        $object->approval_status = ObjectApprovalStatus::WAIT;
+        $object->save();
+
+        return $this->noContent();
+    }
+}

+ 54 - 0
app/Http/Requests/API/Approval/CreateOrUpdateRequest.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace App\Http\Requests\API\Approval;
+
+use App\Models\Enums\ApprovalObjectType;
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Validation\Rules\Enum;
+
+class CreateOrUpdateRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
+     */
+    public function rules(): array
+    {
+        $rule = [];
+
+        if ($this->method() == "POST") {
+            $rule = [
+                ...$rule,
+                'object_type' => [
+                    'required',
+                    new Enum(ApprovalObjectType::class),
+                ],
+                'object_id' => [
+                    'required',
+                    function ($attribute, $value, $fail) {
+                        $exist = ApprovalObjectType::from($this->get("object_type"))
+                            ->modelBuilderAllowed($value)
+                            ->where("company_id", Auth::user()->company_id)
+                            ->where('id', $value)
+                            ->count();
+                        if (! $exist) {
+                            $fail('Resources without permission to access.');
+                        }
+                    }
+                ]
+            ];
+        }
+
+        return $rule;
+    }
+}

+ 27 - 0
app/Models/Approval.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+class Approval extends Model
+{
+    use HasFactory, SoftDeletes;
+
+    protected $fillable = [
+        'approval_flow_id',
+        'object_type',
+        'object_id',
+        'node_level',
+        'users',
+        'remark',
+        'created_by',
+    ];
+
+    public function approvalFlow()
+    {
+        return $this->belongsTo(ApprovalFlow::class)->withTrashed();
+    }
+}

+ 57 - 0
app/Models/Enums/ApprovalObjectType.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace App\Models\Enums;
+
+use App\Models\Container;
+use App\Models\Project;
+use App\Models\Requirement;
+use App\Models\Task;
+
+enum ApprovalObjectType: string
+{
+    case PROJECT = "project";
+
+    case REQUIREMENT = "requirement";
+
+    case TASK = "task";
+
+    case CONTAINER = "container";
+
+
+    public function modelBuilder(): \Illuminate\Database\Eloquent\Builder
+    {
+        return match ($this) {
+            self::PROJECT => Project::query(),
+            self::TASK => Task::query(),
+            self::REQUIREMENT => Requirement::query(),
+            self::CONTAINER => Container::query(),
+        };
+    }
+
+    public function flowType(): ApprovalFlowType
+    {
+        return match ($this) {
+            self::PROJECT => ApprovalFlowType::PROJECT,
+            self::TASK => ApprovalFlowType::TASK,
+            self::REQUIREMENT => ApprovalFlowType::REQUIREMENT,
+            self::CONTAINER => ApprovalFlowType::CONTAINER,
+        };
+    }
+
+    public function modelBuilderAllowed(string $id = null): \Illuminate\Database\Eloquent\Builder
+    {
+        return match ($this) {
+            self::PROJECT => Project::query()->allowed($id),
+            self::TASK => Task::query()->allowed($id),
+            self::REQUIREMENT => Requirement::query(),
+            self::CONTAINER => Container::query()->allowed($id),
+        };
+    }
+
+    public function nameField(): string
+    {
+        return match ($this) {
+            default => "name",
+        };
+    }
+}

+ 14 - 0
app/Models/Enums/ObjectApprovalStatus.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Models\Enums;
+
+enum ObjectApprovalStatus: string
+{
+    case WAIT = 'wait'; //未开始
+
+    case DOING = 'doing'; //进行中
+
+    case DONE = 'done'; //完成
+
+    case CANCEL = 'cancel'; //取消
+}

+ 63 - 0
app/Services/Approval/StoreService.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace App\Services\Approval;
+
+use App\Models\Approval;
+use App\Models\ApprovalFlow;
+use App\Models\Enums\ApprovalFlowObjectType;
+use App\Models\Enums\ApprovalFlowType;
+use App\Models\Enums\ApprovalObjectType;
+use App\Models\Enums\ObjectApprovalStatus;
+use Illuminate\Support\Facades\Auth;
+
+class StoreService
+{
+    public function store()
+    {
+        $request = request();
+
+        $approvalObjectType = ApprovalObjectType::from($request->object_type);
+
+        $approvalObject = $approvalObjectType->modelBuilderAllowed($request->object_id)->findOrFail($request->object_id);
+
+        throw_validation_if(
+            in_array($approvalObject?->approval_status, [ObjectApprovalStatus::DOING->value]),
+            'Do not initiate duplicate approvals'
+        );
+
+        $approvalFlow = $this->getApprovalFlow($approvalObjectType, $approvalObject);
+
+        $firstNode =  $approvalFlow->nodes[1];
+        Approval::query()->create([
+            'approval_flow_id' => $approvalFlow->id,
+            'object_type' => $approvalObjectType->flowType()->value,
+            'object_id' => $approvalObject->id,
+            'node_level' => $firstNode['level'],
+            'users' => sprintf(",%s,", implode(',', $firstNode['approval_users'])),
+            'remark' => $request->remark,
+            'created_by' => Auth::id(),
+        ]);
+
+        $approvalObject->approval_status = ObjectApprovalStatus::DOING;
+        $approvalObject->save();
+    }
+
+    protected function getApprovalFlow(ApprovalObjectType $approvalObjectType, $approvalObject)
+    {
+        $approvalFlow = ApprovalFlow::query()
+            ->where("type", $approvalObjectType->flowType())
+            ->when(
+                $approvalObjectType->flowType() == ApprovalFlowType::TASK,
+                fn ($query) => $query->orWhere([
+                    'object_type' => ApprovalFlowObjectType::PROJECT->value,
+                    'object_id' => $approvalObject->id,
+                ])
+            )
+            ->where("status", 1)
+            ->orderByDesc("created_at")
+            ->first();
+        throw_validation_if(! $approvalFlow, 'Clearly set up approval flow rules first.');
+
+        return $approvalFlow;
+    }
+}

+ 35 - 0
database/migrations/2024_06_24_194033_create_approvals_table.php

@@ -0,0 +1,35 @@
+<?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('approvals', function (Blueprint $table) {
+            $table->id();
+            $table->integer("approval_flow_id")->nullable();
+            $table->string("object_type")->nullable();
+            $table->string("object_id")->nullable()->index();
+            $table->integer("node_level")->nullable();
+            $table->string("users")->nullable();
+            $table->integer("created_by");
+            $table->text("remark")->nullable();
+            $table->softDeletes();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('approvals');
+    }
+};

+ 28 - 0
database/migrations/2024_06_24_201525_add_approval_status_to_projects.php

@@ -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::table('projects', function (Blueprint $table) {
+            $table->string("approval_status", 30)->nullable()->default(\App\Models\Enums\ObjectApprovalStatus::WAIT->value);
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('projects', function (Blueprint $table) {
+            $table->dropColumn(['approval_status']);
+        });
+    }
+};

+ 1 - 0
routes/api.php

@@ -70,6 +70,7 @@ Route::middleware(['auth:sanctum'])->group(function () {
             'user' => API\UserController::class,
             'container' => API\ContainerController::class,
             'approval-flow'  => API\ApprovalFlowController::class,
+            'approval' => API\ApprovalController::class,
         ]);
         Route::apiResource("company", API\CompanyController::class)->only([
             'index', 'show'