From dfa287c6201f0ff8f344868fd843ac58d2d61afa Mon Sep 17 00:00:00 2001 From: cesarLima1 Date: Tue, 12 Mar 2024 10:34:12 -0400 Subject: [PATCH 01/12] feat: TM-697 add Sites shapefiles in export for PD --- ...rtAllProjectDataAsProjectDeveloperController.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/Http/Controllers/V2/Exports/ExportAllProjectDataAsProjectDeveloperController.php b/app/Http/Controllers/V2/Exports/ExportAllProjectDataAsProjectDeveloperController.php index 8107e5042..a8d105f7f 100644 --- a/app/Http/Controllers/V2/Exports/ExportAllProjectDataAsProjectDeveloperController.php +++ b/app/Http/Controllers/V2/Exports/ExportAllProjectDataAsProjectDeveloperController.php @@ -39,6 +39,9 @@ public function __invoke(Request $request, Project $project) rescue(function () use ($project, $zip) { $this->addSiteReportsExports($project, $zip); }); + rescue(function () use ($project, $zip) { + $this->addSiteShapefiles($project, $zip); + }); rescue(function () use ($project, $zip) { $this->addNurseriesExports($project, $zip); }); @@ -66,6 +69,16 @@ private function addSiteReportsExports(Project $project, \ZipArchive $mainZip): } } + private function addSiteShapefiles(Project $project, \ZipArchive $mainZip): void + { + $shapefilesFolder = 'Sites Shapefiles/'; + $mainZip->addEmptyDir($shapefilesFolder); + + foreach ($project->sites as $site) { + $filename = $shapefilesFolder . Str::of($site->name)->replace(['/', '\\'], '-') . '.geojson'; + $mainZip->addFromString($filename, $site->boundary_geojson); + } + } private function addNurseryReportsExports(Project $project, \ZipArchive $mainZip): void { $form = $this->getForm(NurseryReport::class, $project->framework_key); From e09d99d428c75cf983ae78ba82a3f3d875978f1c Mon Sep 17 00:00:00 2001 From: cesarLima1 Date: Tue, 12 Mar 2024 11:40:41 -0400 Subject: [PATCH 02/12] feat: TM-698 add shapefiles export option in export project entity --- ...jectEntityAsProjectDeveloperController.php | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/V2/Exports/ExportProjectEntityAsProjectDeveloperController.php b/app/Http/Controllers/V2/Exports/ExportProjectEntityAsProjectDeveloperController.php index 660ba4fb7..b45d8fba1 100644 --- a/app/Http/Controllers/V2/Exports/ExportProjectEntityAsProjectDeveloperController.php +++ b/app/Http/Controllers/V2/Exports/ExportProjectEntityAsProjectDeveloperController.php @@ -20,9 +20,13 @@ public function __invoke(Request $request, Project $project, string $entity) { ini_set('memory_limit', '-1'); Validator::make(['entity' => $entity], [ - 'entity' => 'required|in:sites,nurseries,project-reports', + 'entity' => 'required|in:sites,nurseries,project-reports,shapefiles', ])->validate(); + if ($entity === 'shapefiles') { + return $this->exportShapefiles($project); + } + $modelClass = $this->getModelClass($entity); $form = $this->getForm($modelClass, $project->framework_key); @@ -63,4 +67,31 @@ private function getModelClass(string $entity) return $model; } + + private function exportShapefiles(Project $project) + { + $filename = public_path('storage/'.Str::of($project->name)->replace(['/', '\\'], '-') . ' Sites Shapefiles - ' . now() . '.zip'); + $zip = new \ZipArchive(); + $zip->open($filename, \ZipArchive::CREATE); + + rescue(function () use ($project, $zip) { + $this->addSiteShapefiles($project, $zip); + }); + + $zip->close(); + + return response()->download($filename)->deleteFileAfterSend(); + } + + private function addSiteShapefiles(Project $project, \ZipArchive $mainZip): void + { + $shapefilesFolder = 'Sites Shapefiles/'; + $mainZip->addEmptyDir($shapefilesFolder); + + foreach ($project->sites as $site) { + $geojsonFilename = $shapefilesFolder . Str::of($site->name)->replace(['/', '\\'], '-') . '.geojson'; + $mainZip->addFromString($geojsonFilename, $site->boundary_geojson); + } + } + } From f3efc0f4cdd781b2e67dc4a2c451214f324a531f Mon Sep 17 00:00:00 2001 From: Limber Mamani <154026979+LimberHope@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:06:08 -0400 Subject: [PATCH 03/12] feat: TM-725 add new fields to v2 sites (#81) * feat: TM-725 add new fields to v2 sites * fix: use helper I18nHelper --- app/Http/Resources/V2/Sites/SiteResource.php | 2 + app/Models/V2/Sites/Site.php | 2 + config/wri/linked-fields.php | 2 + ...2400_seed_options_list_siting_strategy.php | 39 +++++++++++++++++++ ...tion_siting_strategy_to_v2_sites_table.php | 33 ++++++++++++++++ 5 files changed, 78 insertions(+) create mode 100644 database/migrations/2024_03_11_132400_seed_options_list_siting_strategy.php create mode 100644 database/migrations/2024_03_11_132835_add_new_field_description_siting_strategy_to_v2_sites_table.php diff --git a/app/Http/Resources/V2/Sites/SiteResource.php b/app/Http/Resources/V2/Sites/SiteResource.php index 9012d0a3e..fc835578d 100644 --- a/app/Http/Resources/V2/Sites/SiteResource.php +++ b/app/Http/Resources/V2/Sites/SiteResource.php @@ -54,6 +54,8 @@ public function toArray($request) 'updated_at' => $this->updated_at, 'has_monitoring_data' => empty($this->has_monitoring_data) ? false : true, 'seeds_planted_count' => $this->seeds_planted_count, + 'siting_strategy' => $this->siting_strategy, + 'description_siting_strategy' => $this->description_siting_strategy, ]; return $this->appendFilesToResource($data); diff --git a/app/Models/V2/Sites/Site.php b/app/Models/V2/Sites/Site.php index 541eb331d..14cb64ed0 100644 --- a/app/Models/V2/Sites/Site.php +++ b/app/Models/V2/Sites/Site.php @@ -82,6 +82,8 @@ class Site extends Model implements HasMedia, AuditableContract, EntityModel 'aim_number_of_mature_trees', 'land_use_types', 'restoration_strategy', + 'siting_strategy', + 'description_siting_strategy', 'framework_key', 'old_id', 'old_model', diff --git a/config/wri/linked-fields.php b/config/wri/linked-fields.php index c631a7841..a79b1b5da 100644 --- a/config/wri/linked-fields.php +++ b/config/wri/linked-fields.php @@ -537,6 +537,8 @@ 'site-aim-number-of-mature-trees' => ['property' => 'aim_number_of_mature_trees', 'label' => 'Aim number of mature trees', 'input_type' => 'number'], 'site-start-date' => ['property' => 'start_date', 'label' => 'Start date', 'input_type' => 'date'], 'site-end-date' => ['property' => 'end_date', 'label' => 'End date', 'input_type' => 'date'], + 'site-description-siting-strategy' => ['property' => 'description_siting_strategy', 'label' => 'Description siting strategy', 'input_type' => 'text'], + 'site-col-siting-strategy' => ['property' => 'siting_strategy', 'label' => 'Siting Strategy', 'input_type' => 'select', 'multichoice' => false, 'option_list_key' => 'siting-strategy-collection'], ], 'file-collections' => [ 'site-col-media' => ['property' => 'media', 'label' => 'Media', 'input_type' => 'file', 'multichoice' => true], diff --git a/database/migrations/2024_03_11_132400_seed_options_list_siting_strategy.php b/database/migrations/2024_03_11_132400_seed_options_list_siting_strategy.php new file mode 100644 index 000000000..7c4d8a367 --- /dev/null +++ b/database/migrations/2024_03_11_132400_seed_options_list_siting_strategy.php @@ -0,0 +1,39 @@ + ['Concentred','Distributed', 'Hybrid'], + ]; + + foreach ($collections as $key => $items) { + $formOptionList = FormOptionList::create(['key' => $key]); + + foreach ($items as $item) { + $options = FormOptionListOption::create([ + 'form_option_list_id' => $formOptionList->id, + 'label' => $item, + 'slug' => Str::slug($item), + ]); + } + } + + foreach (FormOptionListOption::all() as $option) { + $option->label_id = I18nHelper::generateI18nItem($option, 'label'); + $option->save(); + } + } +} diff --git a/database/migrations/2024_03_11_132835_add_new_field_description_siting_strategy_to_v2_sites_table.php b/database/migrations/2024_03_11_132835_add_new_field_description_siting_strategy_to_v2_sites_table.php new file mode 100644 index 000000000..effef5c98 --- /dev/null +++ b/database/migrations/2024_03_11_132835_add_new_field_description_siting_strategy_to_v2_sites_table.php @@ -0,0 +1,33 @@ +text('description_siting_strategy')->nullable(); + $table->text('siting_strategy')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('v2_sites', function (Blueprint $table) { + $table->dropColumn(['description_siting_strategy, siting_strategy']); + }); + } +} From 1392127abeeef6d33ca9b8acc0239f3dfe2d725f Mon Sep 17 00:00:00 2001 From: Limber Mamani <154026979+LimberHope@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:06:32 -0400 Subject: [PATCH 04/12] feat: change total employees column type to organisation table (#85) --- ...yees_column_type_to_organisation_table.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2024_03_12_161700_change_total_employees_column_type_to_organisation_table.php diff --git a/database/migrations/2024_03_12_161700_change_total_employees_column_type_to_organisation_table.php b/database/migrations/2024_03_12_161700_change_total_employees_column_type_to_organisation_table.php new file mode 100644 index 000000000..2d3ad1801 --- /dev/null +++ b/database/migrations/2024_03_12_161700_change_total_employees_column_type_to_organisation_table.php @@ -0,0 +1,32 @@ +integer('total_employees')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('organisations', function (Blueprint $table) { + $table->tinyInteger('total_employees')->nullable()->change(); + }); + } +} From 2b03a65bd673dd4a1b4eda4daf8e0390f1fb369c Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 14 Mar 2024 14:37:26 -0400 Subject: [PATCH 05/12] [TM-740] Fix up the calculation for trees planted. --- .../NurseryReportsViaNurseryController.php | 2 +- .../ProjectReportsViaProjectController.php | 2 +- .../SiteReportsViaSiteController.php | 2 +- app/Models/Traits/HasReportStatus.php | 10 +++++ app/Models/V2/Projects/Project.php | 42 ++++++++++--------- app/Models/V2/Projects/ProjectReport.php | 4 +- 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/app/Http/Controllers/V2/NurseryReports/NurseryReportsViaNurseryController.php b/app/Http/Controllers/V2/NurseryReports/NurseryReportsViaNurseryController.php index 1740cfb54..53be5171b 100644 --- a/app/Http/Controllers/V2/NurseryReports/NurseryReportsViaNurseryController.php +++ b/app/Http/Controllers/V2/NurseryReports/NurseryReportsViaNurseryController.php @@ -33,7 +33,7 @@ public function __invoke(Request $request, Nursery $nursery): NurseryReportsColl AllowedFilter::exact('framework_key'), ]) ->where('nursery_id', $nursery->id) - ->isComplete(); + ->hasBeenSubmitted(); if (in_array($request->query('sort'), $sortableColumns)) { $qry->allowedSorts($sortableColumns); diff --git a/app/Http/Controllers/V2/ProjectReports/ProjectReportsViaProjectController.php b/app/Http/Controllers/V2/ProjectReports/ProjectReportsViaProjectController.php index 8414a3b95..410008c6a 100644 --- a/app/Http/Controllers/V2/ProjectReports/ProjectReportsViaProjectController.php +++ b/app/Http/Controllers/V2/ProjectReports/ProjectReportsViaProjectController.php @@ -35,7 +35,7 @@ public function __invoke(Request $request, Project $project): ProjectReportsColl AllowedFilter::exact('update_request_status'), ]) ->where('project_id', $project->id) - ->isComplete(); + ->hasBeenSubmitted(); if (in_array($request->query('sort'), $sortableColumns)) { $qry->allowedSorts($sortableColumns); diff --git a/app/Http/Controllers/V2/SiteReports/SiteReportsViaSiteController.php b/app/Http/Controllers/V2/SiteReports/SiteReportsViaSiteController.php index 197ee1242..c93cd08a7 100644 --- a/app/Http/Controllers/V2/SiteReports/SiteReportsViaSiteController.php +++ b/app/Http/Controllers/V2/SiteReports/SiteReportsViaSiteController.php @@ -33,7 +33,7 @@ public function __invoke(Request $request, Site $site): SiteReportsCollection AllowedFilter::exact('framework_key'), ]) ->where('site_id', $site->id) - ->isComplete(); + ->hasBeenSubmitted(); if (in_array($request->query('sort'), $sortableColumns)) { $qry->allowedSorts($sortableColumns); diff --git a/app/Models/Traits/HasReportStatus.php b/app/Models/Traits/HasReportStatus.php index c0d1c404a..f606d498c 100644 --- a/app/Models/Traits/HasReportStatus.php +++ b/app/Models/Traits/HasReportStatus.php @@ -40,6 +40,11 @@ public function supportsNothingToReport(): bool ReportStatusStateMachine::APPROVED => 'Approved', ]; + public const UNSUBMITTED_STATUSES = [ + ReportStatusStateMachine::DUE, + ReportStatusStateMachine::STARTED, + ]; + public const COMPLETE_STATUSES = [ ReportStatusStateMachine::AWAITING_APPROVAL, ReportStatusStateMachine::APPROVED, @@ -60,6 +65,11 @@ public function scopeIsComplete(Builder $query): Builder return $query->whereIn('status', self::COMPLETE_STATUSES); } + public function scopeHasBeenSubmitted(Builder $query): Builder + { + return $query->whereNotIn('status', self::UNSUBMITTED_STATUSES); + } + public function isEditable(): bool { return in_array($this->status, [ReportStatusStateMachine::DUE, ReportStatusStateMachine::STARTED]) || diff --git a/app/Models/V2/Projects/Project.php b/app/Models/V2/Projects/Project.php index ccb27bcf3..edcaa2dc6 100644 --- a/app/Models/V2/Projects/Project.php +++ b/app/Models/V2/Projects/Project.php @@ -23,6 +23,7 @@ use App\Models\V2\Sites\SiteReport; use App\Models\V2\Tasks\Task; use App\Models\V2\TreeSpecies\TreeSpecies; +use App\StateMachines\EntityStatusStateMachine; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -305,29 +306,16 @@ public function getProjectReportsTotalAttribute(): int public function getTreesPlantedCountAttribute(): int { - $siteIds = Site::where('project_id', $this->id) - ->isApproved() - ->pluck('id') - ->toArray(); - - $submissionsIds = SiteReport::whereIn('site_id', $siteIds) - ->isComplete() - ->pluck('id') - ->toArray(); - return TreeSpecies::where('speciesable_type', SiteReport::class) - ->whereIn('speciesable_id', $submissionsIds) + ->whereIn('speciesable_id', $this->submittedSiteReportIds()) ->where('collection', TreeSpecies::COLLECTION_PLANTED) ->sum('amount'); } public function getSeedsPlantedCountAttribute(): int { - $siteIds = $this->sites()->pluck('id')->toArray(); - $submissionsIds = SiteReport::whereIn('site_id', $siteIds)->pluck('id')->toArray(); - return Seeding::where('seedable_type', SiteReport::class) - ->whereIn('seedable_id', $submissionsIds) + ->whereIn('seedable_id', $this->submittedSiteReportIds()) ->sum('amount'); } @@ -351,12 +339,12 @@ public function getWorkdayCountAttribute(): int $sitePaid = SiteReport::whereIn('id', $siteIds) ->where('due_at', '<', now()) - ->isComplete() + ->hasBeenSubmitted() ->sum('workdays_paid'); $siteVolunteer = SiteReport::whereIn('id', $siteIds) ->where('due_at', '<', now()) - ->isComplete() + ->hasBeenSubmitted() ->sum('workdays_volunteer'); return $paid + $volunteer + $sitePaid + $siteVolunteer; @@ -398,12 +386,12 @@ public function getTotalOverdueReportsAttribute(): int ->isIncomplete() ->count(); - $sOverdue = SiteReport::whereIn('id', $siteIds) + $sOverdue = SiteReport::whereIn('site_id', $siteIds) ->where('due_at', '<', now()) ->isIncomplete() ->count(); - $nOverdue = NurseryReport::whereIn('id', $nurseryIds) + $nOverdue = NurseryReport::whereIn('nursery_id', $nurseryIds) ->where('due_at', '<', now()) ->isIncomplete() ->count(); @@ -447,4 +435,20 @@ public function toSearchableArray() 'name' => $this->name, ]; } + + /** + * @return array The array of site report IDs for all reports associated with sites that have been approved, and + * have a report status not in due or started (reports that have been submitted). + */ + private function submittedSiteReportIds(): array + { + // scopes that use status don't work on the HasManyThrough because both Site and SiteReport have + // a status field. + return $this + ->siteReports() + ->where('v2_sites.status', EntityStatusStateMachine::APPROVED) + ->whereNotIn('v2_site_reports.status', SiteReport::UNSUBMITTED_STATUSES) + ->pluck('v2_site_reports.id') + ->toArray(); + } } diff --git a/app/Models/V2/Projects/ProjectReport.php b/app/Models/V2/Projects/ProjectReport.php index d5a5e52e9..375056c7f 100644 --- a/app/Models/V2/Projects/ProjectReport.php +++ b/app/Models/V2/Projects/ProjectReport.php @@ -387,14 +387,14 @@ public function getWorkdaysTotalAttribute(): int $sitePaid = SiteReport::whereIn('id', $siteIds) ->where('due_at', '<', now()) - ->isComplete() + ->hasBeenSubmitted() ->whereMonth('due_at', $month) ->whereYear('due_at', $year) ->sum('workdays_paid'); $siteVolunteer = SiteReport::whereIn('id', $siteIds) ->where('due_at', '<', now()) - ->isComplete() + ->hasBeenSubmitted() ->whereMonth('due_at', $month) ->whereYear('due_at', $year) ->sum('workdays_volunteer'); From f63fbb53208227f1064e90a566eedf14b23fe4e5 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 18 Mar 2024 12:20:32 -0700 Subject: [PATCH 06/12] [TM-724] Fix aggregation of workday totals at the site and project level. --- app/Models/V2/Projects/Project.php | 45 +++++++++++++++--------------- app/Models/V2/Sites/Site.php | 9 ++++-- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/app/Models/V2/Projects/Project.php b/app/Models/V2/Projects/Project.php index edcaa2dc6..310dcba06 100644 --- a/app/Models/V2/Projects/Project.php +++ b/app/Models/V2/Projects/Project.php @@ -32,6 +32,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Facades\DB; use Laravel\Scout\Searchable; use OwenIt\Auditing\Auditable; use OwenIt\Auditing\Contracts\Auditable as AuditableContract; @@ -332,22 +333,15 @@ public function getRegeneratedTreesCountAttribute(): int public function getWorkdayCountAttribute(): int { - $paid = $this->reports()->isApproved()->sum('workdays_paid'); - $volunteer = $this->reports()->isApproved()->sum('workdays_volunteer'); - - $siteIds = $this->sites()->pluck('id')->toArray(); - - $sitePaid = SiteReport::whereIn('id', $siteIds) - ->where('due_at', '<', now()) - ->hasBeenSubmitted() - ->sum('workdays_paid'); - - $siteVolunteer = SiteReport::whereIn('id', $siteIds) - ->where('due_at', '<', now()) - ->hasBeenSubmitted() - ->sum('workdays_volunteer'); - - return $paid + $volunteer + $sitePaid + $siteVolunteer; + $sumQueries = [ + DB::raw("sum(`workdays_paid`) as paid"), + DB::raw("sum(`workdays_volunteer`) as volunteer"), + ]; + $projectTotals = $this->reports()->hasBeenSubmitted()->get($sumQueries)->first(); + // The groupBy is superfluous, but required because Laravel adds "v2_sites.project_id as laravel_through_key" to + // the SQL select. + $siteTotals = $this->submittedSiteReports()->groupBy('v2_sites.project_id')->get($sumQueries)->first(); + return $projectTotals->paid + $projectTotals->volunteer + $siteTotals->paid + $siteTotals->volunteer; } public function getTotalJobsCreatedAttribute(): int @@ -436,6 +430,18 @@ public function toSearchableArray() ]; } + /** + * @return HasManyThrough A relation for all site reports associated with this project that is for an approved + * site, and has a report status past due/started (has been submitted). + */ + private function submittedSiteReports(): HasManyThrough + { + return $this + ->siteReports() + ->where('v2_sites.status', EntityStatusStateMachine::APPROVED) + ->whereNotIn('v2_site_reports.status', SiteReport::UNSUBMITTED_STATUSES); + } + /** * @return array The array of site report IDs for all reports associated with sites that have been approved, and * have a report status not in due or started (reports that have been submitted). @@ -444,11 +450,6 @@ private function submittedSiteReportIds(): array { // scopes that use status don't work on the HasManyThrough because both Site and SiteReport have // a status field. - return $this - ->siteReports() - ->where('v2_sites.status', EntityStatusStateMachine::APPROVED) - ->whereNotIn('v2_site_reports.status', SiteReport::UNSUBMITTED_STATUSES) - ->pluck('v2_site_reports.id') - ->toArray(); + return $this->submittedSiteReports()->pluck('v2_site_reports.id')->toArray(); } } diff --git a/app/Models/V2/Sites/Site.php b/app/Models/V2/Sites/Site.php index 14cb64ed0..9a76d5768 100644 --- a/app/Models/V2/Sites/Site.php +++ b/app/Models/V2/Sites/Site.php @@ -28,6 +28,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Facades\DB; use Laravel\Scout\Searchable; use OwenIt\Auditing\Auditable; use OwenIt\Auditing\Contracts\Auditable as AuditableContract; @@ -291,10 +292,12 @@ public function getRegeneratedTreesCountAttribute(): int public function getWorkdayCountAttribute(): int { - $volunteers = $this->reports()->sum('workdays_volunteer'); - $paid = $this->reports()->sum('workdays_paid'); + $totals = $this->reports()->hasBeenSubmitted()->get([ + DB::raw("sum(`workdays_volunteer`) as volunteer"), + DB::raw("sum(`workdays_paid`) as paid"), + ])->first(); - return $volunteers + $paid; + return $totals->volunteer + $totals->paid; } public function getFrameworkUuidAttribute(): ?string From 5aef2dbf510bd1592856a4da036428be326d610e Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 18 Mar 2024 14:33:14 -0700 Subject: [PATCH 07/12] [TM-724] Null protect count generation for projects or sites that don't yet have any submitted reports. --- app/Models/V2/Projects/Project.php | 2 +- app/Models/V2/Sites/Site.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/V2/Projects/Project.php b/app/Models/V2/Projects/Project.php index 310dcba06..cb857a5ed 100644 --- a/app/Models/V2/Projects/Project.php +++ b/app/Models/V2/Projects/Project.php @@ -341,7 +341,7 @@ public function getWorkdayCountAttribute(): int // The groupBy is superfluous, but required because Laravel adds "v2_sites.project_id as laravel_through_key" to // the SQL select. $siteTotals = $this->submittedSiteReports()->groupBy('v2_sites.project_id')->get($sumQueries)->first(); - return $projectTotals->paid + $projectTotals->volunteer + $siteTotals->paid + $siteTotals->volunteer; + return $projectTotals?->paid + $projectTotals?->volunteer + $siteTotals?->paid + $siteTotals?->volunteer; } public function getTotalJobsCreatedAttribute(): int diff --git a/app/Models/V2/Sites/Site.php b/app/Models/V2/Sites/Site.php index 9a76d5768..a5d2f7491 100644 --- a/app/Models/V2/Sites/Site.php +++ b/app/Models/V2/Sites/Site.php @@ -297,7 +297,7 @@ public function getWorkdayCountAttribute(): int DB::raw("sum(`workdays_paid`) as paid"), ])->first(); - return $totals->volunteer + $totals->paid; + return $totals?->paid + $totals?->volunteer; } public function getFrameworkUuidAttribute(): ?string From 0445e6128716e71c77dfd6cac5f2ccdc44c11327 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 18 Mar 2024 16:24:20 -0700 Subject: [PATCH 08/12] [TM-705] Fix sorting, and add a -due_at default sort. --- .../V2/Projects/ViewProjectTasksController.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/V2/Projects/ViewProjectTasksController.php b/app/Http/Controllers/V2/Projects/ViewProjectTasksController.php index dcff25877..3f03e259e 100644 --- a/app/Http/Controllers/V2/Projects/ViewProjectTasksController.php +++ b/app/Http/Controllers/V2/Projects/ViewProjectTasksController.php @@ -18,12 +18,13 @@ public function __invoke(Request $request, Project $project): TasksCollection $sortableColumns = [ 'status', '-status', - 'period_key', '-period_key', + 'due_at', '-due_at', ]; - $query = Task::with(['project']) - ->isIncomplete() - ->where('project_id', $project->id); + $query = QueryBuilder::for(Task::class) + ->with('project') + ->where('project_id', $project->id) + ->defaultSort('-due_at'); if (in_array($request->query('sort'), $sortableColumns)) { $query->allowedSorts($sortableColumns); From 96191be7a7ab3a24cf663c44dc0b9e652a5ab240 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 18 Mar 2024 16:33:22 -0700 Subject: [PATCH 09/12] [TM-705] Total reporting tasks is no longer needed on the client. --- app/Http/Resources/V2/Projects/ProjectLiteResource.php | 1 - app/Models/V2/Projects/Project.php | 5 ----- routes/api_v2.php | 3 ++- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/Http/Resources/V2/Projects/ProjectLiteResource.php b/app/Http/Resources/V2/Projects/ProjectLiteResource.php index 0e6e72210..a2bcb625d 100644 --- a/app/Http/Resources/V2/Projects/ProjectLiteResource.php +++ b/app/Http/Resources/V2/Projects/ProjectLiteResource.php @@ -21,7 +21,6 @@ public function toArray($request) 'organisation' => new OrganisationLiteResource($this->organisation), 'planting_start_date' => $this->planting_start_date, 'has_monitoring_data' => $this->has_monitoring_data, - 'total_reporting_tasks' => $this->total_reporting_tasks, 'project_reports_total' => $this->project_reports_total, ]; diff --git a/app/Models/V2/Projects/Project.php b/app/Models/V2/Projects/Project.php index ccb27bcf3..10c75554c 100644 --- a/app/Models/V2/Projects/Project.php +++ b/app/Models/V2/Projects/Project.php @@ -416,11 +416,6 @@ public function getHasMonitoringDataAttribute(): int return $this->monitoring()->count() > 0 ? 1 : 0; } - public function getTotalReportingTasksAttribute(): int - { - return $this->tasks()->isIncomplete()->count(); - } - public function getFrameworkUuidAttribute(): ?string { return $this->framework ? $this->framework->uuid : null; diff --git a/routes/api_v2.php b/routes/api_v2.php index 880a19250..9d5a8e245 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -388,6 +388,8 @@ Route::get('/', IndexMyActionsController::class); Route::put('/{action}/complete', CompleteActionController::class); }); + + Route::get('/projects', ViewMyProjectsController::class); }); Route::post('/users/resend', [AuthController::class, 'resendByEmail']); @@ -519,7 +521,6 @@ Route::delete('/{coreTeamLeader}', DeleteCoreTeamLeaderController::class); }); -Route::get('/my/projects', ViewMyProjectsController::class); Route::prefix('projects')->group(function () { Route::delete('/{project}', SoftDeleteProjectController::class); Route::get('/{project}/tasks', ViewProjectTasksController::class); From 7a0c9b0374ab2ffd9b1f9c235e893f14f7c50748 Mon Sep 17 00:00:00 2001 From: Jose Carlos Laura Ramirez Date: Wed, 20 Mar 2024 14:13:28 -0400 Subject: [PATCH 10/12] [TM-697] fix: use storage path for temporal file (#96) --- .../ExportAllProjectDataAsProjectDeveloperController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/V2/Exports/ExportAllProjectDataAsProjectDeveloperController.php b/app/Http/Controllers/V2/Exports/ExportAllProjectDataAsProjectDeveloperController.php index a8d105f7f..64900d363 100644 --- a/app/Http/Controllers/V2/Exports/ExportAllProjectDataAsProjectDeveloperController.php +++ b/app/Http/Controllers/V2/Exports/ExportAllProjectDataAsProjectDeveloperController.php @@ -23,7 +23,7 @@ public function __invoke(Request $request, Project $project) $form = $this->getForm(Project::class, $project->framework_key); $this->authorize('export', [Project::class, $form, $project]); - $filename = public_path('storage/'.Str::of($project->name)->replace(['/', '\\'], '-') . ' full export - ' . now() . '.zip'); + $filename = storage_path('./'.Str::of($project->name)->replace(['/', '\\'], '-') . ' full export - ' . now() . '.zip'); $zip = new \ZipArchive(); $zip->open($filename, \ZipArchive::CREATE); From 33ce9862a10e2fb89c9a08945739da782c545769 Mon Sep 17 00:00:00 2001 From: Jose Carlos Laura Ramirez Date: Wed, 20 Mar 2024 14:13:54 -0400 Subject: [PATCH 11/12] [TM-698] fix: use storage path for temporal file (#95) --- .../Exports/ExportProjectEntityAsProjectDeveloperController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/V2/Exports/ExportProjectEntityAsProjectDeveloperController.php b/app/Http/Controllers/V2/Exports/ExportProjectEntityAsProjectDeveloperController.php index b45d8fba1..dcef642b4 100644 --- a/app/Http/Controllers/V2/Exports/ExportProjectEntityAsProjectDeveloperController.php +++ b/app/Http/Controllers/V2/Exports/ExportProjectEntityAsProjectDeveloperController.php @@ -70,7 +70,7 @@ private function getModelClass(string $entity) private function exportShapefiles(Project $project) { - $filename = public_path('storage/'.Str::of($project->name)->replace(['/', '\\'], '-') . ' Sites Shapefiles - ' . now() . '.zip'); + $filename = storage_path('./'.Str::of($project->name)->replace(['/', '\\'], '-') . ' Sites Shapefiles - ' . now() . '.zip'); $zip = new \ZipArchive(); $zip->open($filename, \ZipArchive::CREATE); From 0f131055a4b46fdf7257082676cb0c1c6b66c3ac Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 20 Mar 2024 15:57:45 -0700 Subject: [PATCH 12/12] [TM-779] Allow application exports to run without memory limit. --- app/Jobs/GenerateApplicationExportJob.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Jobs/GenerateApplicationExportJob.php b/app/Jobs/GenerateApplicationExportJob.php index 1a699382c..6328f30ec 100644 --- a/app/Jobs/GenerateApplicationExportJob.php +++ b/app/Jobs/GenerateApplicationExportJob.php @@ -30,6 +30,8 @@ public function __construct(FundingProgramme $fundingProgramme) public function handle() { + ini_set('memory_limit', '-1'); + $name = 'exports/' . $this->fundingProgramme->name . ' Export - ' . now() . '.csv'; $export = (new ApplicationExport($this->fundingProgramme->applications()->getQuery(), $this->fundingProgramme));