Browse Source

Merge branch 'plan-management' of kyle/autocde2.0 into dev

Mo 1 year ago
parent
commit
5f46c5dd92

+ 18 - 5
app/Http/Controllers/API/PlanController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\API;
 
 use App\Http\Controllers\Controller;
 use App\Http\Requests\API\Plan\CreateOrUpdateRequest;
+use App\Http\Resources\API\PlanResource;
 use App\Models\Plan;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
@@ -13,9 +14,11 @@ class PlanController extends Controller
     /**
      * Display a listing of the resource.
      */
-    public function index()
+    public function index(Request $request)
     {
-        //
+        $plans = Plan::filter($request->all())->simplePaginate();
+
+        return PlanResource::collection($plans);
     }
 
     /**
@@ -50,9 +53,15 @@ class PlanController extends Controller
     /**
      * Update the specified resource in storage.
      */
-    public function update(Request $request, string $id)
+    public function update(CreateOrUpdateRequest $request, string $id)
     {
-        //
+        $plan = Plan::findOrFail($id);
+
+        $plan->fill($request->all());
+
+        $plan->save();
+
+        return $this->noContent();
     }
 
     /**
@@ -60,6 +69,10 @@ class PlanController extends Controller
      */
     public function destroy(string $id)
     {
-        //
+        $plan = Plan::findOrFail($id);
+
+        $plan->delete();
+
+        return $this->noContent();
     }
 }

+ 2 - 2
app/Http/Controllers/API/ProjectController.php

@@ -8,7 +8,7 @@ use App\Http\Requests\API\Project\LinkRequirementByPlanRequest;
 use App\Http\Requests\API\Project\LinkRequirementRequest;
 use App\Http\Requests\API\Project\PostponeRequest;
 use App\Http\Requests\API\Project\UnlinkRequirementRequest;
-use App\Http\Resources\API\PlanResource;
+use App\Http\Resources\API\SimplePlanResource;
 use App\Http\Resources\API\ProjectRequirementResource;
 use App\Http\Resources\API\ProjectResource;
 use App\Http\Resources\API\RequirementResource;
@@ -239,7 +239,7 @@ class ProjectController extends Controller
     {
         $project = Project::findOrFail($id);
 
-        return PlanResource::collection($project->plans);
+        return SimplePlanResource::collection($project->plans);
     }
 
     public function requirement(string $id)

+ 26 - 0
app/Http/Controllers/API/RequirementController.php

@@ -4,6 +4,9 @@ namespace App\Http\Controllers\API;
 
 use App\Http\Controllers\Controller;
 use App\Http\Requests\API\Requirement\CreateOrUpdateRequest;
+use App\Http\Requests\API\Requirement\LinkPlanRequest;
+use App\Http\Resources\API\AssetRequirementResource;
+use App\Models\Plan;
 use App\Models\Requirement;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
@@ -18,6 +21,16 @@ class RequirementController extends Controller
         //
     }
 
+    public function byAsset(string $assetId)
+    {
+        $requirements = Requirement::query()
+            ->with(['createdBy', 'plan'])
+            ->where('asset_id', $assetId)
+            ->simplePaginate();
+
+        return AssetRequirementResource::collection($requirements);
+    }
+
     /**
      * Store a newly created resource in storage.
      */
@@ -62,4 +75,17 @@ class RequirementController extends Controller
     {
         //
     }
+
+    public function linkPlan(LinkPlanRequest $request, string $planId)
+    {
+        $plan = Plan::query()->findOrFail($planId);
+
+        Requirement::query()->where("asset_id", $plan->asset_id)
+            ->whereIn('id', $request->get("requirement_ids"))
+            ->update([
+                'plan_id' => $plan->id,
+            ]);
+
+        return $this->noContent();
+    }
 }

+ 31 - 0
app/Http/Requests/API/Requirement/LinkPlanRequest.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Http\Requests\API\Requirement;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class LinkPlanRequest 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
+    {
+        return [
+            'requirement_ids' => [
+                'required',
+                'array',
+            ]
+        ];
+    }
+}

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

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

+ 5 - 0
app/Http/Resources/API/PlanResource.php

@@ -17,6 +17,11 @@ class PlanResource extends JsonResource
         return [
             'id' => $this->id,
             'title' => $this->title,
+            'requirement_total' => $this->requirements()->count(),
+            'project_total' => $this->projects()->count(),
+            'begin' => $this->begin,
+            'end' => $this->end,
+            'description' => $this->description,
         ];
     }
 }

+ 22 - 0
app/Http/Resources/API/SimplePlanResource.php

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

+ 30 - 0
app/ModelFilters/PlanFilter.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\ModelFilters;
+
+use Carbon\Carbon;
+use EloquentFilter\ModelFilter;
+
+class PlanFilter extends ModelFilter
+{
+    /**
+    * Related Models that have ModelFilters as well as the method on the ModelFilter
+    * As [relationMethod => [input_key1, input_key2]].
+    *
+    * @var array
+    */
+    public $relations = [];
+
+    public function expired($expired): PlanFilter
+    {
+        if (! in_array($expired, ['yes', 'no'])) {
+            return $this;
+        }
+
+        return $this->when($expired == "yes", function ($query) {
+            return $query->where('end', "<=", Carbon::now()->toDateString());
+        })->when($expired == "no", function ($query) {
+            return $query->where('end', ">", Carbon::now()->toDateString())->orWhereNull('end');
+        });
+    }
+}

+ 8 - 2
app/Models/Plan.php

@@ -3,12 +3,13 @@
 namespace App\Models;
 
 use App\Models\Scopes\CompanyScope;
+use EloquentFilter\Filterable;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 
 class Plan extends Model
 {
-    use HasFactory;
+    use HasFactory, Filterable;
 
     protected $fillable = [
         'title',
@@ -30,6 +31,11 @@ class Plan extends Model
 
     public function requirements()
     {
-        return $this->belongsToMany(Requirement::class, 'plan_requirement', 'plan_id', 'requirement_id');
+        return $this->hasMany(Requirement::class);
+    }
+
+    public function projects()
+    {
+        return $this->belongsToMany(Project::class, 'project_plan', 'plan_id', 'project_id');
     }
 }

+ 13 - 0
app/Models/Requirement.php

@@ -2,6 +2,7 @@
 
 namespace App\Models;
 
+use App\Models\Scopes\CompanyScope;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 
@@ -26,8 +27,20 @@ class Requirement extends Model
         'mailto' => 'array',
     ];
 
+    protected static function booted()
+    {
+        parent::booted(); // TODO: Change the autogenerated stub
+
+        static::addGlobalScope(new CompanyScope);
+    }
+
     public function createdBy(): \Illuminate\Database\Eloquent\Relations\BelongsTo
     {
         return $this->belongsTo(User::class, 'created_by');
     }
+
+    public function plan(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+    {
+        return $this->belongsTo(Plan::class);
+    }
 }

+ 28 - 0
database/migrations/2024_01_26_131036_add_plan_id_to_requirements_table.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('requirements', function (Blueprint $table) {
+            $table->integer("plan_id")->nullable();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('requirements', function (Blueprint $table) {
+            $table->dropColumn("plan_id");
+        });
+    }
+};

+ 3 - 0
routes/api.php

@@ -34,6 +34,9 @@ Route::middleware(['auth:sanctum'])->group(function () {
         'plan' => API\PlanController::class,
     ]);
 
+    Route::get("requirement/{asset_id}/asset", [API\RequirementController::class, "byAsset"])->name("requirement.byAsset");
+    Route::patch("requirement/{plan_id}/plan", [API\RequirementController::class, "linkPlan"])->name("requirement.linkPlan");
+
     Route::patch("project/{project}/closed", [API\ProjectController::class, "closed"])->name("project.closed");
     Route::patch("project/{project}/start", [API\ProjectController::class, "start"])->name("project.start");
     Route::patch("project/{project}/pause", [API\ProjectController::class, "pause"])->name("project.pause");

+ 48 - 0
tests/Feature/API/PlanTest.php

@@ -16,4 +16,52 @@ class PlanTest extends TestCase
 
         $response->assertStatus(201);
     }
+
+    public function test_asset_group_list()
+    {
+        Plan::factory(30)->create();
+
+        $response = $this->get(route('plan.index'));
+
+        $response->assertStatus(200)
+            ->assertJsonStructure([
+                'data' => [
+                    '*' => [
+                        'id',
+                        'title',
+                        'requirement_total',
+                        'project_total',
+                        'begin',
+                        'end',
+                        'description',
+                    ]
+                ]
+            ]);
+    }
+
+    public function test_plan_update(): void
+    {
+        $plan = Plan::factory()->create();
+
+        $form = Plan::factory()->make();
+
+        $response = $this->put(route('plan.update', ['plan' => $plan->id]), $form->toArray());
+
+        $response->assertStatus(204);
+
+        $newAsset = Plan::find($plan->id);
+
+        $this->assertEquals($form->name, $newAsset->name);
+    }
+
+    public function test_plan_delete(): void
+    {
+        $plan = Plan::factory()->create();
+
+        $response = $this->delete(route('plan.destroy', ['plan' => $plan->id]));
+
+        $response->assertStatus(204);
+
+        $this->assertNull(Plan::find($plan->id));
+    }
 }

+ 1 - 2
tests/Feature/API/ProjectTest.php

@@ -244,6 +244,7 @@ class ProjectTest extends TestCase
 
         $requirement = Requirement::factory()->make();
         $requirement->asset_id = $plan->asset_id;
+        $requirement->plan_id = $plan->id;
         $requirement->save();
 
         PlanRequirement::create([
@@ -280,8 +281,6 @@ class ProjectTest extends TestCase
 
         $response = $this->get(route('project.requirement', ['project' => $project->id]));
 
-        dump($response->getContent());
-
         $response->assertStatus(200)
             ->assertJsonStructure($this->simplePaginateResponseStructure([
                 'id', 'title', 'created_by', 'status'

+ 44 - 0
tests/Feature/API/RequirementTest.php

@@ -2,7 +2,10 @@
 
 namespace Tests\Feature\API;
 
+use App\Models\Asset;
+use App\Models\Plan;
 use App\Models\Requirement;
+use Database\Factories\AssetFactory;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Illuminate\Foundation\Testing\WithFaker;
 use Tests\Feature\TestCase;
@@ -17,4 +20,45 @@ class RequirementTest extends TestCase
 
         $response->assertStatus(201);
     }
+
+    public function test_requirement_query_by_site_id(): void
+    {
+        $asset = AssetFactory::new()->create();
+
+        Requirement::factory(30)->create([
+            'asset_id' => $asset->id,
+        ]);
+
+        $response = $this->get(route('requirement.byAsset', ['asset_id' => $asset->id]));
+
+        $response->assertStatus(200)->assertJsonStructure($this->simplePaginateResponseStructure([
+            'id',
+            'priority',
+            'title',
+            'created_by',
+            'plan',
+            'status',
+        ]));
+    }
+
+    public function test_link_plan(): void
+    {
+        $asset = Asset::factory()->create();
+
+        $plan = Plan::factory()->create([
+            'asset_id' => $asset->id,
+        ]);
+
+        $requirementIds = [];
+
+        for ($i = 0; $i < 10; ++$i) {
+            $requirementIds[] = Requirement::factory()->create(['asset_id' => $asset->id])?->id;
+        }
+
+        $response = $this->patch(route('requirement.linkPlan', ['plan_id' => $plan->id]), [
+            'requirement_ids' => $requirementIds,
+        ]);
+
+        $response->assertStatus(204);
+    }
 }