Browse Source

Action history and field converters

moell 11 months ago
parent
commit
27ebec7b87

+ 23 - 0
app/Http/Controllers/API/ActionController.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Http\Controllers\API;
+
+use App\Http\Controllers\Controller;
+use App\Models\Enums\ActionObjectType;
+use App\Repositories\ActionRepository;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+
+class ActionController extends Controller
+{
+    public function history(string $objectType, string $objectId)
+    {
+        $actionObjectType = ActionObjectType::from($objectType);
+
+        $actionObjectType->modelBuilder()->where("company_id", Auth::user()->company_id)->findOrFail($objectId);
+
+        return $this->success([
+            'data' => ActionRepository::actionWithHistory($actionObjectType, $objectId),
+        ]);
+    }
+}

+ 5 - 0
app/Models/Action.php

@@ -20,4 +20,9 @@ class Action extends Model
     {
         return $this->belongsTo(User::class, "created_by");
     }
+
+    public function histories()
+    {
+        return $this->hasMany(History::class);
+    }
 }

+ 10 - 0
app/Models/Enums/ActionObjectType.php

@@ -6,6 +6,8 @@ use App\Models\Asset;
 use App\Models\Plan;
 use App\Models\Project;
 use App\Models\Task;
+use App\Services\History\Detector\DetectorContact;
+use App\Services\History\Detector\ProjectDetector;
 
 enum ActionObjectType: string
 {
@@ -34,4 +36,12 @@ enum ActionObjectType: string
             self::PLAN => "title",
         };
     }
+
+    public function detectorClassName(): ?string
+    {
+        return match ($this) {
+            ActionObjectType::PROJECT => ProjectDetector::class,
+            default => null
+        };
+    }
 }

+ 56 - 0
app/Repositories/ActionRepository.php

@@ -98,6 +98,7 @@ class ActionRepository
             'object_id' => $action['object_id'],
             'object_type' => $action['object_type'],
             'object_name' => data_get($objectNames, sprintf("%s.%d", $action['object_type'], $action['object_id'])),
+            'comment' => $action['comment'],
         ];
     }
 
@@ -156,4 +157,59 @@ class ActionRepository
 
         return $objectNames;
     }
+
+    public static function actionWithHistory(ActionObjectType $actionObjectType, string $objectId): array
+    {
+        $actions = Action::query()
+            ->with(['histories', 'createdBy'])
+            ->where("object_type", $actionObjectType->value)
+            ->where("object_id", $objectId)
+            ->orderBy("created_at")
+            ->get();
+
+        $objectNames = self::objectNamesGroupByType($actions);
+
+        $items = [];
+        foreach ($actions as $action) {
+            $item = self::actionFormat($action->toArray(), $objectNames);
+            $item['histories'] = self::formatHistories($actionObjectType, $action->histories);
+
+            $items[] = $item;
+        }
+
+        return $items;
+    }
+
+    public static function formatHistories(ActionObjectType $actionObjectType, Collection $histories): array
+    {
+        $detector = $actionObjectType->detectorClassName();
+
+        $items = [];
+        foreach ($histories as $history) {
+            $labelKey = sprintf("fields.%s", $history->field);
+
+            $item = [
+                'field' => $history->field,
+                'field_label' => app('translator')->has($labelKey) ? __($labelKey) : $history->field,
+                'new' => self::coverFieldValue($detector, $history->field, $history->new),
+                'old' => self::coverFieldValue($detector, $history->field, $history->old),
+                'diff' => (string)$history->diff,
+            ];
+
+            $items[] = $item;
+        }
+
+        return $items;
+    }
+
+    protected static function coverFieldValue(string $detector, string $field, string $value): mixed
+    {
+        if (! $detector) {
+            return $value;
+        }
+
+        $converter = call_user_func([$detector, "converter"], $field);
+
+        return $converter ? $converter->handle($value) : $value;
+    }
 }

+ 16 - 0
app/Services/History/Converter/ModelEnumConverter.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Services\History\Converter;
+
+class ModelEnumConverter implements ConverterContact
+{
+    public function __construct(protected string $langFieldPath)
+    {}
+
+    public function handle(mixed $value)
+    {
+        $labelKey = sprintf("model-enums.%s.%s", $this->langFieldPath, $value);
+
+        return app('translator')->has($labelKey) ? __($labelKey) : $value;
+    }
+}

+ 11 - 1
app/Services/History/Converter/WhitelistConverter.php

@@ -2,10 +2,20 @@
 
 namespace App\Services\History\Converter;
 
+use App\Models\User;
+
 class WhitelistConverter implements ConverterContact
 {
     public function handle(mixed $value)
     {
-        // TODO: Implement handle() method.
+        $ids = array_filter(explode(",", $value));
+
+        if (! $ids) {
+            return null;
+        }
+
+        $users = User::query()->whereIn("id", $ids)->pluck("name");
+
+        return $users ? implode(",", $users->toArray()) : null;
     }
 }

+ 2 - 0
app/Services/History/Detector/ProjectDetector.php

@@ -3,6 +3,7 @@
 namespace App\Services\History\Detector;
 
 use App\Services\History\Converter\ConverterContact;
+use App\Services\History\Converter\ModelEnumConverter;
 use App\Services\History\Converter\WhitelistConverter;
 
 class ProjectDetector implements DetectorContact
@@ -35,6 +36,7 @@ class ProjectDetector implements DetectorContact
     {
         return match ($field) {
             "whitelist" => new WhitelistConverter(),
+            "status" => new ModelEnumConverter("project.status"),
             default => null
         };
     }

+ 2 - 5
app/Services/History/ModelChangeDetector.php

@@ -3,17 +3,13 @@
 namespace App\Services\History;
 
 use App\Models\Enums\ActionObjectType;
-use App\Services\History\Detector\ProjectDetector;
 use Illuminate\Database\Eloquent\Model;
 
 class ModelChangeDetector
 {
     public static function detector(ActionObjectType $objectType, Model $model): array
     {
-        $detector = match ($objectType) {
-            ActionObjectType::PROJECT => ProjectDetector::class,
-            default => null
-        };
+        $detector = $objectType->detectorClassName();
 
         if (! $detector) {
             return [];
@@ -34,6 +30,7 @@ class ModelChangeDetector
 
             $items[] = [
                 'old' => $model->getOriginal($field),
+                'field' => $field,
                 'new' => $model->$field,
                 'diff' => in_array($field, $diffFields) ? text_diff($model->getOriginal($field), $model->$field) : null,
             ];

+ 17 - 0
lang/en/model-enums.php

@@ -0,0 +1,17 @@
+<?php
+
+use \App\Models\Enums\ProjectStatus;
+
+return [
+    'project' => [
+        'status' => [
+            ProjectStatus::DOING->value => "In progress",
+            ProjectStatus::WAIT->value => "Not Submitted",
+            ProjectStatus::DONE->value => "A-Approved & B-Approved w/comment",
+            ProjectStatus::PAUSE->value => "C-Amendment & Resubmission Req’d",
+            ProjectStatus::CLOSED->value => "D-Rejected",
+            ProjectStatus::CANCEL->value => "Cancelled",
+            ProjectStatus::PENDING_REVIEW->value => "Pending for Approval",
+        ]
+    ]
+];

+ 2 - 0
routes/api.php

@@ -111,5 +111,7 @@ Route::middleware(['auth:sanctum'])->group(function () {
         Route::post("task-batch-create", [API\TaskController::class, "batchStore"])->name("task.batch-store");
 
         Route::get("project-tree/{project_id}", [API\ProjectController::class, "treeIndex"])->name("project.project-tree");
+
+        Route::get("action/{object_type}/history/{object_id}", [API\ActionController::class, "history"])->name("action.history");
     });
 });