Browse Source

approval flow management

moell 8 months ago
parent
commit
2ad1786a16

+ 124 - 0
app/Http/Controllers/API/ApprovalFlowController.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace App\Http\Controllers\API;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\API\ApprovalFlow\CreateOrUpdateRequest;
+use App\Http\Resources\API\ApprovalFlowDetailResource;
+use App\Http\Resources\API\ApprovalFlowResource;
+use App\Models\ApprovalFlow;
+use App\Models\User;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+
+class ApprovalFlowController extends Controller
+{
+    public function index()
+    {
+        $approvalFlows = ApprovalFlow::query()->with(['createdBy'])->orderByDesc("id")->paginate();
+
+        return ApprovalFlowResource::collection($approvalFlows);
+    }
+
+    public function store(CreateOrUpdateRequest $request)
+    {
+        $nodes = $this->getApprovalNodes($request);
+
+        $approvalFlow = new ApprovalFlow();
+        $approvalFlow->mergeFillable([
+            'company_id'
+        ]);
+
+        $approvalFlow->fill([
+            ...$request->all(),
+            'company_id' => Auth::user()->company_id,
+            'created_by' => Auth::id(),
+            'nodes' => $nodes,
+        ]);
+        $approvalFlow->save();
+
+        return $this->created();
+    }
+
+    protected function getApprovalNodes(Request $request)
+    {
+        $nodes = [];
+        foreach ($request->nodes as $node) {
+            $userCount = User::query()->whereIn("id", $node['approval_users'])->count();
+
+            throw_validation_if($userCount != count($node['approval_users']), "The selected approver does not exist");
+
+            $nodes[$node['level']] = $node;
+        }
+
+        throw_validation_if(max(array_keys($nodes)) != count($nodes), "The level field is incorrect");
+
+        return $nodes;
+    }
+
+    public function update(CreateOrUpdateRequest $request, string $id)
+    {
+        $approvalFlow = ApprovalFlow::query()->findOrFail($id);
+
+        $nodes = $this->getApprovalNodes($request);
+
+        $nodeIsChange = false;
+        if (count($nodes) != count($approvalFlow->nodes)) {
+            $nodeIsChange = true;
+        } else {
+            foreach ($approvalFlow->nodes as $node) {
+                $oldNodeUsers = $node['approval_users'];
+                $nowNodeUsers = $nodes[$node['level']]['approval_users'];
+
+                sort($oldNodeUsers);
+                sort($nowNodeUsers);
+
+                if (implode(',', $oldNodeUsers) != implode(',', $nowNodeUsers)) {
+                    $nodeIsChange = true;
+                    break;
+                }
+            }
+        }
+
+        if ($nodeIsChange) {
+            $newApprovalFlow = new ApprovalFlow();
+            $newApprovalFlow->mergeFillable([
+                'company_id'
+            ]);
+
+            $newApprovalFlow->fill([
+                'name' => $request->name,
+                'object_type' => $approvalFlow->object_type,
+                'object_id' => $approvalFlow->object_id,
+                'company_id' => Auth::user()->company_id,
+                'created_by' => Auth::id(),
+                'nodes' => $nodes,
+            ]);
+            $newApprovalFlow->save();
+
+            $approvalFlow->delete();
+        } else {
+            $approvalFlow->name = $request->name;
+            $approvalFlow->nodes = $nodes;
+            $approvalFlow->save();
+        }
+
+        return $this->noContent();
+    }
+
+    public function show(string $id)
+    {
+        $approvalFlow = ApprovalFlow::query()->findOrFail($id);
+
+        return new ApprovalFlowDetailResource($approvalFlow);
+    }
+
+    public function destroy(string $id)
+    {
+        $approvalFlow = ApprovalFlow::query()->findOrFail($id);
+
+        $approvalFlow->delete();
+
+        return $this->noContent();
+    }
+}

+ 64 - 0
app/Http/Requests/API/ApprovalFlow/CreateOrUpdateRequest.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Http\Requests\API\ApprovalFlow;
+
+use App\Models\Enums\ApprovalFlowObjectType;
+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
+    {
+        $rules =  [
+            "name" => "required|max:50",
+            "nodes" => [
+                'required',
+                'array',
+                'min:1',
+            ],
+            "nodes.*.name" => "required",
+            "nodes.*.approval_users" => "required|array",
+            "nodes.*.level" => "required|numeric",
+        ];
+
+        if ($this->method() == "POST") {
+            $rules = [
+                ...$rules,
+                "object_type" => [
+                    'required',
+                    new Enum(ApprovalFlowObjectType::class),
+                ],
+                "object_id" => [
+                    'required',
+                    function ($attribute, $value, $fail) {
+                        $exist = ApprovalFlowObjectType::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 $rules;
+    }
+}

+ 52 - 0
app/Http/Resources/API/ApprovalFlowDetailResource.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Http\Resources\API;
+
+use App\Models\User;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class ApprovalFlowDetailResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        $nodeUsers = [];
+        foreach ($this->nodes as $node) {
+            $nodeUsers = [
+                ...$nodeUsers,
+                ...$node['approval_users'],
+            ];
+        }
+
+        $users = User::query()->whereIn("id", $nodeUsers)->get()->keyBy("id");
+
+        $nodes = [];
+        foreach ($this->nodes as $node) {
+            $userObjects = [];
+            foreach ($node['approval_users'] as $userId) {
+                $userObjects[] = new UserProfileResource($users->get($userId));
+            }
+
+            $node['approval_users'] = $userObjects;
+
+            $nodes[] = $node;
+        }
+
+        return [
+            "id" => $this->id,
+            "name" => $this->name,
+            "object_type" => $this->object_type,
+            "object_id" => $this->object_id,
+            "nodes" => $nodes,
+            "status" => $this->status,
+            "created_by" => new UserProfileResource($this->createdBy),
+            "created_at" => (string)$this->created_at,
+            "updated_at" => (string)$this->updated_at,
+        ];
+    }
+}

+ 26 - 0
app/Http/Resources/API/ApprovalFlowResource.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Http\Resources\API;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class ApprovalFlowResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'name' => $this->name,
+            'object_type' => $this->object_type,
+            'object_id' => $this->object_id,
+            'created_at' => (string)$this->created_at,
+            'created_by' => new UserProfileResource($this->createdBy),
+        ];
+    }
+}

+ 31 - 0
app/Models/ApprovalFlow.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Models;
+
+use App\Models\Scopes\CompanyScope;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+class ApprovalFlow extends Model
+{
+    use HasFactory, SoftDeletes;
+
+    protected $fillable = [
+        'name', 'object_type', 'object_id', 'nodes', 'created_by',
+    ];
+
+    protected $casts = [
+        'nodes' => 'array',
+    ];
+
+    protected static function booted(): void
+    {
+        static::addGlobalScope(new CompanyScope);
+    }
+
+    public function createdBy()
+    {
+        return $this->belongsTo(User::class, "created_by");
+    }
+}

+ 51 - 0
app/Models/Enums/ApprovalFlowObjectType.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace App\Models\Enums;
+
+use App\Models\Asset;
+use App\Models\Container;
+use App\Models\Plan;
+use App\Models\Project;
+use App\Models\Requirement;
+use App\Models\Task;
+use App\Services\History\Detector\AssetDetector;
+use App\Services\History\Detector\ContainerContentDetector;
+use App\Services\History\Detector\ContainerDetector;
+use App\Services\History\Detector\ProjectDetector;
+use App\Services\History\Detector\RequirementDetector;
+use App\Services\History\Detector\TaskDetector;
+
+enum ApprovalFlowObjectType: string
+{
+    case ASSET = "asset";
+
+    case PROJECT = "project";
+
+    case REQUIREMENT = "requirement";
+
+    case TASK = "task";
+
+    case CONTAINER = "container";
+
+    public function modelBuilder(): \Illuminate\Database\Eloquent\Builder
+    {
+        return match ($this) {
+            self::ASSET => Asset::query(),
+            self::PROJECT => Project::query(),
+            self::TASK => Task::query(),
+            self::REQUIREMENT => Requirement::query(),
+            self::CONTAINER => Container::query(),
+        };
+    }
+
+    public function modelBuilderAllowed(string $id = null): \Illuminate\Database\Eloquent\Builder
+    {
+        return match ($this) {
+            self::ASSET => Asset::query()->allowed(),
+            self::PROJECT => Project::query()->allowed($id),
+            self::TASK => Task::query()->allowed($id),
+            self::REQUIREMENT => Requirement::query(),
+            self::CONTAINER => Container::query()->allowed($id),
+        };
+    }
+}

+ 35 - 0
database/migrations/2024_06_22_131902_create_approval_flows_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('approval_flows', function (Blueprint $table) {
+            $table->id();
+            $table->string("name", 50);
+            $table->integer("company_id");
+            $table->string("object_type", 20);
+            $table->integer("object_id");
+            $table->json("nodes");
+            $table->boolean("status")->default(true);
+            $table->integer("created_by")->nullable();
+            $table->softDeletes();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('approval_flows');
+    }
+};

+ 2 - 1
routes/api.php

@@ -69,6 +69,7 @@ Route::middleware(['auth:sanctum'])->group(function () {
             'department' => API\DepartmentController::class,
             'user' => API\UserController::class,
             'container' => API\ContainerController::class,
+            'approval-flow'  => API\ApprovalFlowController::class,
         ]);
         Route::apiResource("company", API\CompanyController::class)->only([
             'index', 'show'
@@ -201,6 +202,6 @@ Route::middleware(['auth:sanctum'])->group(function () {
         Route::get("share-file/{object_type}/{object_id}", [API\ShareFileController::class, "byObject"])
             ->name("share-file.by-object");
 
-        Route::get("department-user-index",[API\DepartmentController::class,"departmentUserIndex"])->name("department.user-index");;
+        Route::get("department-user-index",[API\DepartmentController::class,"departmentUserIndex"])->name("department.user-index");
     });
 });