Skip to content

Commit

Permalink
[TM-857] Audit Log and Comment Feature for Project/Site/Polygons (#254)
Browse files Browse the repository at this point in the history
* add status table

* add attachment table

* add audit-status + site-polygon endpoints

* add swagger definitions for audit status and polygon endpoints

* fix: problem with parameter in open api definition

* fix openapi definitions and test execution

* implement polymorphic association in Audit Status

* fix merge errors and remove duplicated definition

* add AuditStatus attachments with MediaModel

* remove unused methods and files

* remove GetPolygonByProjectController

* replace auditable_id and auditable_type with entity_name

* add AuditStatusRequest to improve endpoint readability

* improve swagger definition

* update swagger definition

* replace EntityModel by AuditableModel

* rename variable

* remove unused table

* remove unused definition and update swagger

* change AuditStatus upload file policy

* address corrections on AuditableModel

* use ModelInterfaceBindingMiddleware on store audit controller

* fix typo and update references in swagger

* rename status table to audit-statuese

* change table name to use underscore

* [TM-843] add endpoint to determine wether a site can be approved (#274)

* add endpoint to determine wether a site can be approved

* improve code for checking site approval

* [TM-944] Enable polygon audit log (#273)

* Add endpoint for listing SitePolygons for a given project

* fix problem with parameters missing in definition

* [TM-941] Audit Log request support (#275)

* add endpoint for updating status

* change EntityModel -> AuditableModel and add missing policy

* move criteria site to helper for reuse

* add criteria check for site polygon approval

* move endpoint to auditable path

* add a policy for updating Site Polygons

* fix problem with parameters missing in definitio

* improve query for criteria site
  • Loading branch information
pachonjcl authored Jun 12, 2024
1 parent 569440f commit f553187
Show file tree
Hide file tree
Showing 35 changed files with 1,145 additions and 26 deletions.
16 changes: 16 additions & 0 deletions app/Helpers/GeometryHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Models\V2\PolygonGeometry;
use App\Models\V2\Projects\Project;
use App\Models\V2\Sites\CriteriaSite;
use Illuminate\Support\Facades\Log;

class GeometryHelper
Expand Down Expand Up @@ -117,4 +118,19 @@ public static function getPolygonsBbox($polygonsIds)

return [$minX, $minY, $maxX, $maxY];
}

public static function getCriteriaDataForPolygonGeometry($polygonGeometry)
{
return CriteriaSite::whereIn(
'id',
$polygonGeometry
->criteriaSite()
->groupBy('criteria_id')
->selectRaw('max(id) as latest_id')
)->get([
'criteria_id',
'valid',
'created_at as latest_created_at',
]);
}
}
25 changes: 25 additions & 0 deletions app/Http/Controllers/V2/AuditStatus/GetAuditStatusController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Http\Controllers\V2\AuditStatus;

use App\Http\Controllers\Controller;
use App\Http\Resources\V2\AuditStatusResource;
use App\Models\V2\AuditableModel;
use Illuminate\Http\Request;

class GetAuditStatusController extends Controller
{
public function __invoke(Request $request, AuditableModel $auditable)
{
$auditStatuses = $auditable->auditStatuses()
->orderBy('updated_at', 'desc')
->orderBy('created_at', 'desc')
->get();

foreach ($auditStatuses as $auditStatus) {
$auditStatus->entity_name = $auditable->getAuditableNameAttribute();
}

return AuditStatusResource::collection($auditStatuses);
}
}
34 changes: 34 additions & 0 deletions app/Http/Controllers/V2/AuditStatus/StoreAuditStatusController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App\Http\Controllers\V2\AuditStatus;

use App\Http\Controllers\Controller;
use App\Http\Requests\V2\AuditStatus\AuditStatusCreateRequest;
use App\Http\Resources\V2\AuditStatusResource;
use App\Models\Traits\SaveAuditStatusTrait;
use App\Models\V2\AuditableModel;
use App\Models\V2\AuditStatus\AuditStatus;

class StoreAuditStatusController extends Controller
{
use SaveAuditStatusTrait;

public function __invoke(AuditStatusCreateRequest $auditStatusCreateRequest, AuditableModel $auditable): AuditStatusResource
{
$body = $auditStatusCreateRequest->all();

if ($body['type'] === 'change-request') {
AuditStatus::where([
['auditable_id', $auditable->id],
['type', 'change-request'],
['is_active', true],
])->update(['is_active' => false]);
$auditStatus = $this->saveAuditStatus(get_class($auditable), $auditable->id, $body['status'], $body['comment'], $body['type'], $body['is_active'], $body['request_removed']);
} else {
$auditStatus = $this->saveAuditStatus(get_class($auditable), $auditable->id, $body['status'], $body['comment'], $body['type']);
}
$auditStatus->entity_name = $auditable->name;

return new AuditStatusResource($auditStatus);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace App\Http\Controllers\V2\Auditable;

use App\Helpers\GeometryHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\V2\AuditStatus\AuditStatusUpdateRequest;
use App\Models\Traits\SaveAuditStatusTrait;
use App\Models\V2\AuditableModel;
use App\Models\V2\AuditStatus\AuditStatus;
use App\Models\V2\Sites\SitePolygon;
use App\Services\PolygonService;

class UpdateAuditableStatusController extends Controller
{
use SaveAuditStatusTrait;

public function __invoke(AuditStatusUpdateRequest $request, AuditableModel $auditable)
{
$this->authorize('update', $auditable);

if (! $this->canChangeStatus($auditable, $request->status)) {
return response()->json(['message' => 'Cannot change status'], 400);
}

$body = $request->all();
$status = $body['status'];

$auditable->status = $status;
$auditable->save();

if (isset($body['status'])) {
$this->saveAuditStatus(get_class($auditable), $auditable->id, $status, $body['comment'], $body['type']);
} elseif (isset($body['is_active'])) {
AuditStatus::where('auditable_id', $auditable->id)
->where('type', $body['type'])
->where('is_active', true)
->update(['is_active' => false]);
$this->saveAuditStatus(get_class($auditable), $auditable->id, $status, $body['comment'], $body['type'], $body['is_active'], $body['request_removed']);
}

return $auditable;
}

private function canChangeStatus($auditable, $status): bool
{
switch(get_class($auditable)) {
case 'App\Models\V2\Sites\Site':
return $this->canChangeSiteStatusTo($auditable, $status);
case 'App\Models\V2\Sites\SitePolygon':
return $this->canChangeSitePolygonStatusTo($auditable, $status);
default:
return true;
}
}

private function canChangeSiteStatusTo($auditable, $status)
{
if ($status === 'approved') {
return ! SitePolygon::where('site_id', $auditable->id)->where('status', 'approved')->exists();
}

return true;
}

private function canChangeSitePolygonStatusTo($sitePolygon, $status)
{
if ($status === 'approved') {
$geometry = $sitePolygon->polygonGeometry()->first();

if ($geometry === null) {
return false;
}

$criteriaList = GeometryHelper::getCriteriaDataForPolygonGeometry($geometry);

if (empty($criteriaList)) {
return false;
}

$criteriaList = array_filter($criteriaList, function ($criteria) {
return $criteria['criteria_id'] !== PolygonService::ESTIMATED_AREA_CRITERIA_ID;
});

$canApprove = true;
foreach ($criteriaList as $criteria) {
if (! $criteria['valid']) {
$canApprove = false;

break;
}
}

return $canApprove;
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Http\Controllers\V2\Polygons;

use App\Http\Controllers\Controller;
use App\Http\Resources\V2\SitePolygon\SitePolygonResource;
use App\Models\V2\Projects\Project;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class ViewAllSitesPolygonsForProjectController extends Controller
{
public function __invoke(Request $request, Project $project): ResourceCollection
{
return SitePolygonResource::collection($project->sitePolygons()->get());
}
}
18 changes: 18 additions & 0 deletions app/Http/Controllers/V2/Sites/SiteCheckApproveController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Http\Controllers\V2\Sites;

use App\Http\Controllers\Controller;
use App\Models\V2\Sites\Site;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class SiteCheckApproveController extends Controller
{
public function __invoke(Request $request, Site $site): JsonResource
{
$hasNonApproved = $site->sitePolygons()->where('status', '!=', 'approved')->exists();

return new JsonResource(['can_approve' => $hasNonApproved]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace App\Http\Controllers\V2\Terrafund;

use App\Helpers\GeometryHelper;
use App\Http\Controllers\Controller;
use App\Models\V2\PolygonGeometry;
use App\Models\V2\Sites\CriteriaSite;
use App\Models\V2\Sites\SitePolygon;
use App\Models\V2\WorldCountryGeneralized;
use App\Services\PolygonService;
Expand Down Expand Up @@ -313,30 +313,12 @@ public function getCriteriaData(Request $request)
return response()->json(['error' => 'Polygon not found for the given UUID'], 404);
}

// Fetch data from criteria_site with distinct criteria_id based on the latest created_at
$criteriaDataQuery = 'SELECT criteria_id, MAX(created_at) AS latest_created_at
FROM criteria_site
WHERE polygon_id = ?
GROUP BY criteria_id';
$criteriaList = GeometryHelper::getCriteriaDataForPolygonGeometry($geometry);

$criteriaData = DB::select($criteriaDataQuery, [$uuid]);

if (empty($criteriaData)) {
if (empty($criteriaList)) {
return response()->json(['error' => 'Criteria data not found for the given polygon ID'], 404);
}

// Determine the validity of each criteria
$criteriaList = [];
foreach ($criteriaData as $criteria) {
$criteriaId = $criteria->criteria_id;
$valid = CriteriaSite::where(['polygon_id' => $uuid, 'criteria_id' => $criteriaId])->orderBy('created_at', 'desc')->select('valid')->first()?->valid;
$criteriaList[] = [
'criteria_id' => $criteriaId,
'latest_created_at' => $criteria->latest_created_at,
'valid' => $valid,
];
}

return response()->json(['polygon_id' => $uuid, 'criteria_list' => $criteriaList]);
}

Expand Down
4 changes: 4 additions & 0 deletions app/Http/Middleware/ModelInterfaceBindingMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Middleware;

use App\Models\V2\AuditStatus\AuditStatus;
use App\Models\V2\Forms\Form;
use App\Models\V2\Forms\FormQuestionOption;
use App\Models\V2\FundingProgramme;
Expand All @@ -14,6 +15,7 @@
use App\Models\V2\Projects\ProjectReport;
use App\Models\V2\Sites\Site;
use App\Models\V2\Sites\SiteMonitoring;
use App\Models\V2\Sites\SitePolygon;
use App\Models\V2\Sites\SiteReport;
use Closure;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -49,6 +51,8 @@ class ModelInterfaceBindingMiddleware
'form-question-option' => FormQuestionOption::class,
'project-monitoring' => ProjectMonitoring::class,
'site-monitoring' => SiteMonitoring::class,
'site-polygon' => SitePolygon::class,
'audit-status' => AuditStatus::class,
];

private static array $typeSlugsCache = [];
Expand Down
24 changes: 24 additions & 0 deletions app/Http/Requests/V2/AuditStatus/AuditStatusCreateRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Http\Requests\V2\AuditStatus;

use Illuminate\Foundation\Http\FormRequest;

class AuditStatusCreateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'type' => 'sometimes|nullable|string',
'comment' => 'sometimes|nullable|string',
'status' => 'sometimes|nullable|string',
'is_active' => 'sometimes|nullable|boolean',
'request_removed' => 'sometimes|nullable|boolean',
];
}
}
24 changes: 24 additions & 0 deletions app/Http/Requests/V2/AuditStatus/AuditStatusUpdateRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Http\Requests\V2\AuditStatus;

use Illuminate\Foundation\Http\FormRequest;

class AuditStatusUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'type' => 'sometimes|nullable|string',
'comment' => 'sometimes|nullable|string',
'status' => 'sometimes|nullable|string',
'is_active' => 'sometimes|nullable|boolean',
'request_removed' => 'sometimes|nullable|boolean',
];
}
}
33 changes: 33 additions & 0 deletions app/Http/Resources/V2/AuditStatusResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App\Http\Resources\V2;

use Illuminate\Http\Resources\Json\JsonResource;

class AuditStatusResource extends JsonResource
{
/**
* @param Request $request
* @return array
*/
public function toArray($request)
{
$data = [
'id' => $this->id,
'uuid' => $this->uuid,
'entity_name' => $this->entity_name,
'status' => $this->status,
'comment' => $this->comment,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'type' => $this->type,
'is_submitted' => $this->is_submitted,
'is_active' => $this->is_active,
'request_removed' => $this->request_removed,
'date_created' => $this->date_created,
'created_by' => $this->created_by,
];

return $this->appendFilesToResource($data);
}
}
Loading

0 comments on commit f553187

Please sign in to comment.