From 7adb6932e25c84310a2d1576e08a067d79b862a9 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Mon, 20 May 2024 15:51:57 -0700 Subject: [PATCH 01/12] [TM-680] Include sum of total seedlings grown to date in project report rows. --- app/Exports/V2/EntityExport.php | 8 ++++-- app/Models/V2/Projects/ProjectReport.php | 36 ++++++++++++------------ 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/app/Exports/V2/EntityExport.php b/app/Exports/V2/EntityExport.php index db157c250..01d692962 100644 --- a/app/Exports/V2/EntityExport.php +++ b/app/Exports/V2/EntityExport.php @@ -76,7 +76,7 @@ protected function getAttachedMappedForEntity($entity): array $entity->due_at ?? null, ]; - if (in_array($this->form->type, ['nursery', 'nursery-report','site', 'site-report', 'project-report'])) { + if (in_array($this->form->type, ['nursery', 'nursery-report', 'site', 'site-report', 'project-report'])) { $mapped[] = $entity->project->ppc_external_id ?? $entity->project->id ?? null; } @@ -84,6 +84,7 @@ protected function getAttachedMappedForEntity($entity): array $mapped[] = $entity->project->uuid ?? null; if($this->form->framework_key === 'ppc') { $mapped[] = $entity->seedlings_grown ?? null; + $mapped[] = $entity->seedlings_grown_to_date ?? null; } } if ($this->form->type === 'nursery-report') { @@ -94,8 +95,8 @@ protected function getAttachedMappedForEntity($entity): array if ($this->form->type === 'site-report') { $mapped[] = $entity->site->ppc_external_id ?? $entity->site->id ?? null; $mapped[] = $entity->site->name ?? null; - $sumTreeSPecies = $entity->treeSpecies()->sum('amount'); - $mapped[] = $sumTreeSPecies > 0 ? $sumTreeSPecies : null; + $sumTreeSpecies = $entity->treeSpecies()->sum('amount'); + $mapped[] = $sumTreeSpecies > 0 ? $sumTreeSpecies : null; $mapped[] = $entity->site->trees_planted_count ?? null; if($this->form->framework_key === 'ppc') { $sumSeeding = $entity->seedings()->sum('amount'); @@ -126,6 +127,7 @@ protected function getAttachedHeadingsForEntity(): array if ($this->form->type === 'project-report') { $initialHeadings[] = 'project_uuid'; if($this->form->framework_key === 'ppc') { + $initialHeadings[] = 'total_seedlings_grown_report'; $initialHeadings[] = 'total_seedlings_grown'; } } diff --git a/app/Models/V2/Projects/ProjectReport.php b/app/Models/V2/Projects/ProjectReport.php index a25f34874..cd2835fb7 100644 --- a/app/Models/V2/Projects/ProjectReport.php +++ b/app/Models/V2/Projects/ProjectReport.php @@ -13,8 +13,6 @@ use App\Models\Traits\HasWorkdays; use App\Models\Traits\UsesLinkedFields; use App\Models\V2\MediaModel; -use App\Models\V2\Nurseries\Nursery; -use App\Models\V2\Nurseries\NurseryReport; use App\Models\V2\Organisation; use App\Models\V2\Polygon; use App\Models\V2\ReportModel; @@ -290,30 +288,32 @@ public function getReportTitleAttribute(): string public function getSeedlingsGrownAttribute(): int { if ($this->framework_key == 'ppc') { - return $this->treeSpecies() - ->sum('amount'); + return $this->treeSpecies()->sum('amount'); } if ($this->framework_key == 'terrafund') { - if (empty($this->due_at)) { + if (empty($this->task_id)) { return 0; } - $month = $this->due_at->month; - $year = $this->due_at->year; - $nurseryIds = Nursery::where('project_id', data_get($this->project, 'id')) - ->isApproved() - ->pluck('id') - ->toArray(); - - if (count($nurseryIds) > 0) { - return NurseryReport::whereIn('nursery_id', $nurseryIds) - ->whereMonth('due_at', $month) - ->whereYear('due_at', $year) - ->sum('seedlings_young_trees'); - } + return $this->task->nurseryReports()->sum('seedlings_young_trees'); + } + + return 0; + } + + public function getSeedlingsGrownToDateAttribute(): int + { + if ($this->framework_key == 'ppc') { + return TreeSpecies::where('speciesable_type', ProjectReport::class) + ->whereIn( + 'speciesable_id', + $this->project->reports()->where('created_at', '<=', $this->created_at)->select('id') + ) + ->sum('amount'); } + // this attribute is currently only used for PPC report exports. return 0; } From ab2c35cebdfc556a7b7704e8c8c5cf79134dff46 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 21 May 2024 12:45:36 -0700 Subject: [PATCH 02/12] [TM-738] Support fewer than 0 for "A natural regeneration" --- .../2024_05_21_193901_float_regeneration.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 database/migrations/2024_05_21_193901_float_regeneration.php diff --git a/database/migrations/2024_05_21_193901_float_regeneration.php b/database/migrations/2024_05_21_193901_float_regeneration.php new file mode 100644 index 000000000..76e566d94 --- /dev/null +++ b/database/migrations/2024_05_21_193901_float_regeneration.php @@ -0,0 +1,27 @@ +float('a_nat_regeneration')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('v2_sites', function (Blueprint $table) { + $table->unsignedInteger('a_nat_regeneration')->change(); + }); + } +}; From bf0950d40d49cb121745495ac5b5b7570353c577 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 21 May 2024 21:12:38 -0700 Subject: [PATCH 03/12] [TM-898] Update application rejection email copy. --- app/Mail/FormSubmissionRejected.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Mail/FormSubmissionRejected.php b/app/Mail/FormSubmissionRejected.php index afcdb9580..db36d23c1 100644 --- a/app/Mail/FormSubmissionRejected.php +++ b/app/Mail/FormSubmissionRejected.php @@ -6,13 +6,13 @@ class FormSubmissionRejected extends Mail { public function __construct(String $feedback = null) { - $this->subject = 'Application Rejected'; - $this->title = 'Your application has been rejected'; - $this->body = - 'Your application has been rejected.'; + $this->subject = 'Application Status Update'; + $this->title = 'THANK YOU FOR YOUR APPLICATION'; + $this->body = 'After careful review, our team has decided your application will not move forward.'; if (! is_null($feedback)) { - $this->body = 'Your application has been rejected. Please see comments below:

' . - e($feedback); + $this->body .= + ' Please see the comments below for more details or any follow-up resources.

' . + e($feedback); } $this->transactional = true; } From 261032788766aec118cd5ca856dddee2ad3a6b55 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 21 May 2024 22:04:35 -0700 Subject: [PATCH 04/12] [TM-928] Include a link to the TM Admin view of sites, nurseries, site reports and nursery reports in export. --- app/Exports/V2/EntityExport.php | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/app/Exports/V2/EntityExport.php b/app/Exports/V2/EntityExport.php index 01d692962..7481c9a8a 100644 --- a/app/Exports/V2/EntityExport.php +++ b/app/Exports/V2/EntityExport.php @@ -4,6 +4,7 @@ use App\Models\V2\Forms\Form; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Str; use Maatwebsite\Excel\Concerns\FromQuery; use Maatwebsite\Excel\Concerns\WithHeadings; use Maatwebsite\Excel\Concerns\WithMapping; @@ -69,12 +70,24 @@ protected function getAttachedMappedForEntity($entity): array $mapped = [ $entity->ppc_external_id ?? $entity->old_id ?? $entity->id ?? null, $entity->uuid, + ]; + + if (in_array($this->form->type, ['site', 'nursery', 'site-report', 'nursery-report'])) { + $frontEndUrl = config('app.front_end'); + // Our environment variable definitions are inconsistent. + if (! Str::endsWith($frontEndUrl, '/')) { + $frontEndUrl .= '/'; + } + $mapped[] = $frontEndUrl . 'admin#/' . Str::camel($entity->shortName) . '/' . $entity->uuid . '/show'; + } + + $mapped = array_merge($mapped, [ $organisation->readable_type ?? null, $organisation->name ?? null, $entity->project->name ?? null, $entity->status ?? null, $entity->due_at ?? null, - ]; + ]); if (in_array($this->form->type, ['nursery', 'nursery-report', 'site', 'site-report', 'project-report'])) { $mapped[] = $entity->project->ppc_external_id ?? $entity->project->id ?? null; @@ -110,15 +123,19 @@ protected function getAttachedMappedForEntity($entity): array protected function getAttachedHeadingsForEntity(): array { - $initialHeadings = [ - 'id', - 'uuid', + $initialHeadings = ['id', 'uuid']; + + if (in_array($this->form->type, ['site', 'nursery', 'site-report', 'nursery-report'])) { + $initialHeadings[] = 'link_to_terramatch'; + } + + $initialHeadings = array_merge($initialHeadings, [ 'organization-readable_type', 'organization-name', 'project_name', 'status', 'due_date', - ]; + ]); if (in_array($this->form->type, ['nursery', 'nursery-report','site', 'site-report', 'project-report'])) { $initialHeadings[] = 'project-id'; From 664fc6a171fa5e94b079423c8f165fcf7602e9a4 Mon Sep 17 00:00:00 2001 From: cesarLima1 Date: Wed, 22 May 2024 16:08:28 -0400 Subject: [PATCH 05/12] feat: create Point Geometry table --- ..._14_151653_create_point_geometry_table.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 database/migrations/2024_05_14_151653_create_point_geometry_table.php diff --git a/database/migrations/2024_05_14_151653_create_point_geometry_table.php b/database/migrations/2024_05_14_151653_create_point_geometry_table.php new file mode 100644 index 000000000..a9853ef4d --- /dev/null +++ b/database/migrations/2024_05_14_151653_create_point_geometry_table.php @@ -0,0 +1,34 @@ +id(); + $table->uuid('uuid')->unique(); + $table->geometry('geom')->nullable(); + $table->decimal('est_area', 15, 2)->nullable(); + $table->string('created_by')->nullable(); + $table->string('last_modified_by')->nullable(); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('point_geometry'); + } +}; \ No newline at end of file From dfd1abf70d473d6c6fae278506a1c2e15a957035 Mon Sep 17 00:00:00 2001 From: cesarLima1 Date: Wed, 22 May 2024 16:23:54 -0400 Subject: [PATCH 06/12] fix: make lint-fix --- .../2024_05_14_151653_create_point_geometry_table.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/database/migrations/2024_05_14_151653_create_point_geometry_table.php b/database/migrations/2024_05_14_151653_create_point_geometry_table.php index a9853ef4d..445d288ce 100644 --- a/database/migrations/2024_05_14_151653_create_point_geometry_table.php +++ b/database/migrations/2024_05_14_151653_create_point_geometry_table.php @@ -4,8 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ +return new class () extends Migration { /** * Run the migrations. */ @@ -31,4 +30,4 @@ public function down(): void { Schema::dropIfExists('point_geometry'); } -}; \ No newline at end of file +}; From 080f9d5a4a3debbf2807e207583e3af1c0892d8d Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Thu, 23 May 2024 10:01:41 -0700 Subject: [PATCH 07/12] [TM-738] For the UI, round values greater than 1 to a whole int. --- app/Http/Resources/V2/Sites/SiteResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Resources/V2/Sites/SiteResource.php b/app/Http/Resources/V2/Sites/SiteResource.php index 6daba0c71..eb98d950f 100644 --- a/app/Http/Resources/V2/Sites/SiteResource.php +++ b/app/Http/Resources/V2/Sites/SiteResource.php @@ -33,7 +33,7 @@ public function toArray($request) 'survival_rate_planted' => $this->survival_rate_planted, 'direct_seeding_survival_rate' => $this->direct_seeding_survival_rate, 'a_nat_regeneration_trees_per_hectare' => $this->a_nat_regeneration_trees_per_hectare, - 'a_nat_regeneration' => $this->a_nat_regeneration, + 'a_nat_regeneration' => $this->a_nat_regeneration > 1 ? intval(round($this->a_nat_regeneration)) : $this->a_nat_regeneration, 'hectares_to_restore_goal' => $this->hectares_to_restore_goal, 'landscape_community_contribution' => $this->landscape_community_contribution, 'planting_pattern' => $this->planting_pattern, From ae57d61fb2a5c0e3ab52bdd3f0e239651250dbad Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 24 May 2024 08:54:02 -0700 Subject: [PATCH 08/12] [TM-938] Aggregate demographics for the site and project workday_count attributes. --- app/Models/V2/Projects/Project.php | 24 +++++----- app/Models/V2/Sites/Site.php | 15 ++++--- .../factories/V2/Workdays/WorkdayFactory.php | 12 +++++ tests/Unit/Models/V2/Projects/ProjectTest.php | 44 +++++++++++++++++++ tests/Unit/Models/V2/Sites/SiteTest.php | 23 ++++++++++ 5 files changed, 100 insertions(+), 18 deletions(-) diff --git a/app/Models/V2/Projects/Project.php b/app/Models/V2/Projects/Project.php index 393e2aa7a..c1d76cb03 100644 --- a/app/Models/V2/Projects/Project.php +++ b/app/Models/V2/Projects/Project.php @@ -25,6 +25,8 @@ use App\Models\V2\Sites\SiteReport; use App\Models\V2\Tasks\Task; use App\Models\V2\TreeSpecies\TreeSpecies; +use App\Models\V2\Workdays\Workday; +use App\Models\V2\Workdays\WorkdayDemographic; use App\StateMachines\EntityStatusStateMachine; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -34,7 +36,6 @@ 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; @@ -358,16 +359,17 @@ public function getRegeneratedTreesCountAttribute(): int public function getWorkdayCountAttribute(): int { - $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; + return WorkdayDemographic::whereIn( + 'workday_id', + Workday::where('workdayable_type', SiteReport::class) + ->whereIn('workdayable_id', $this->submittedSiteReports()->select('v2_site_reports.id')) + ->select('id') + )->orWhereIn( + 'workday_id', + Workday::where('workdayable_type', ProjectReport::class) + ->whereIn('workdayable_id', $this->reports()->hasBeenSubmitted()->select('id')) + ->select('id') + )->gender()->sum('amount') ?? 0; } public function getTotalJobsCreatedAttribute(): int diff --git a/app/Models/V2/Sites/Site.php b/app/Models/V2/Sites/Site.php index a98a8b84e..d3dda28d5 100644 --- a/app/Models/V2/Sites/Site.php +++ b/app/Models/V2/Sites/Site.php @@ -20,6 +20,8 @@ use App\Models\V2\Seeding; use App\Models\V2\Stratas\Strata; use App\Models\V2\TreeSpecies\TreeSpecies; +use App\Models\V2\Workdays\Workday; +use App\Models\V2\Workdays\WorkdayDemographic; use App\StateMachines\ReportStatusStateMachine; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -29,7 +31,6 @@ 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; @@ -302,12 +303,12 @@ public function getRegeneratedTreesCountAttribute(): int public function getWorkdayCountAttribute(): int { - $totals = $this->reports()->hasBeenSubmitted()->get([ - DB::raw('sum(`workdays_volunteer`) as volunteer'), - DB::raw('sum(`workdays_paid`) as paid'), - ])->first(); - - return $totals?->paid + $totals?->volunteer; + return WorkdayDemographic::whereIn( + 'workday_id', + Workday::where('workdayable_type', SiteReport::class) + ->whereIn('workdayable_id', $this->reports()->hasBeenSubmitted()->select('id')) + ->select('id') + )->gender()->sum('amount') ?? 0; } public function getFrameworkUuidAttribute(): ?string diff --git a/database/factories/V2/Workdays/WorkdayFactory.php b/database/factories/V2/Workdays/WorkdayFactory.php index e6a19f119..ce4669559 100644 --- a/database/factories/V2/Workdays/WorkdayFactory.php +++ b/database/factories/V2/Workdays/WorkdayFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories\V2\Workdays; +use App\Models\V2\Projects\ProjectReport; use App\Models\V2\Sites\SiteReport; use App\Models\V2\Workdays\Workday; use Illuminate\Database\Eloquent\Factories\Factory; @@ -22,4 +23,15 @@ public function definition() 'collection' => $this->faker->randomElement(array_keys(Workday::SITE_COLLECTIONS)), ]; } + + public function projectReport(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'workdayable_type' => ProjectReport::class, + 'workdayable_id' => ProjectReport::factory()->create(), + 'collection' => $this->faker->randomElement(array_keys(Workday::PROJECT_COLLECTION)), + ]; + }); + } } diff --git a/tests/Unit/Models/V2/Projects/ProjectTest.php b/tests/Unit/Models/V2/Projects/ProjectTest.php index 27a3a1455..249157afc 100644 --- a/tests/Unit/Models/V2/Projects/ProjectTest.php +++ b/tests/Unit/Models/V2/Projects/ProjectTest.php @@ -7,6 +7,10 @@ use App\Models\V2\Projects\ProjectMonitoring; use App\Models\V2\Projects\ProjectReport; use App\Models\V2\Sites\Site; +use App\Models\V2\Sites\SiteReport; +use App\Models\V2\Workdays\Workday; +use App\Models\V2\Workdays\WorkdayDemographic; +use App\StateMachines\EntityStatusStateMachine; use Tests\TestCase; class ProjectTest extends TestCase @@ -107,6 +111,46 @@ public function test_it_deletes_its_own_project_monitorings(string $permission, } } + public function test_workday_count() + { + $project = Project::factory()->ppc()->create(); + + // The amounts are all prime so that it's easy to tell what got counted and what didn't when there's an error + + // Unapproved site (doesn't count toward workday count) + $site = Site::factory()->ppc()->create(['project_id' => $project->id, 'status' => EntityStatusStateMachine::AWAITING_APPROVAL]); + $report = SiteReport::factory()->ppc()->create(['site_id' => $site->id, 'status' => EntityStatusStateMachine::APPROVED]); + $workday = Workday::factory()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 3]); + + // Approved site + $site = Site::factory()->ppc()->create(['project_id' => $project->id, 'status' => EntityStatusStateMachine::APPROVED]); + $report = SiteReport::factory()->ppc()->create(['site_id' => $site->id, 'status' => EntityStatusStateMachine::APPROVED]); + $workday = Workday::factory()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 5]); + $report = SiteReport::factory()->ppc()->create(['site_id' => $site->id, 'status' => EntityStatusStateMachine::AWAITING_APPROVAL]); + $workday = Workday::factory()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 7]); + // Unsubmitted report (doesn't count toward workday count) + $report = SiteReport::factory()->ppc()->create(['site_id' => $site->id, 'status' => EntityStatusStateMachine::STARTED]); + $workday = Workday::factory()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 11]); + + $report = ProjectReport::factory()->ppc()->create(['project_id' => $project->id, 'status' => EntityStatusStateMachine::APPROVED]); + $workday = Workday::factory()->projectReport()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 13]); + $report = ProjectReport::factory()->ppc()->create(['project_id' => $project->id, 'status' => EntityStatusStateMachine::AWAITING_APPROVAL]); + $workday = Workday::factory()->projectReport()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 17]); + // Unsubmitted report (doesn't count toward workday count) + $report = ProjectReport::factory()->ppc()->create(['project_id' => $project->id, 'status' => EntityStatusStateMachine::STARTED]); + $workday = Workday::factory()->projectReport()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 19]); + + // 42 = 5 and 7 from the approved site's reports and 13 and 17 from the project reports + $this->assertEquals(42, $project->workday_count); + } + public static function permissionsDataProvider() { return [ diff --git a/tests/Unit/Models/V2/Sites/SiteTest.php b/tests/Unit/Models/V2/Sites/SiteTest.php index afd58959a..ca9d25c90 100644 --- a/tests/Unit/Models/V2/Sites/SiteTest.php +++ b/tests/Unit/Models/V2/Sites/SiteTest.php @@ -5,6 +5,9 @@ use App\Models\V2\Sites\Site; use App\Models\V2\Sites\SiteMonitoring; use App\Models\V2\Sites\SiteReport; +use App\Models\V2\Workdays\Workday; +use App\Models\V2\Workdays\WorkdayDemographic; +use App\StateMachines\EntityStatusStateMachine; use Tests\TestCase; class SiteTest extends TestCase @@ -57,6 +60,26 @@ public function test_it_deletes_its_own_site_monitorings(string $permission, str } } + public function test_workday_count() + { + $site = Site::factory()->ppc()->create(); + + $report = SiteReport::factory()->ppc()->create(['site_id' => $site->id, 'status' => EntityStatusStateMachine::APPROVED]); + $workday = Workday::factory()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 3]); + + $report = SiteReport::factory()->ppc()->create(['site_id' => $site->id, 'status' => EntityStatusStateMachine::AWAITING_APPROVAL]); + $workday = Workday::factory()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 5]); + + // Unsubmitted report (doesn't count toward workday count) + $report = SiteReport::factory()->ppc()->create(['site_id' => $site->id, 'status' => EntityStatusStateMachine::STARTED]); + $workday = Workday::factory()->create(['workdayable_id' => $report->id]); + WorkdayDemographic::factory()->create(['workday_id' => $workday->id, 'amount' => 7]); + + $this->assertEquals(8, $site->workday_count); + } + public static function permissionsDataProvider() { return [ From 1b9c9d84bdbbf4acdbb3479bc319ae6180eb1e21 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 24 May 2024 10:23:10 -0700 Subject: [PATCH 09/12] [TM-929] Support framework key on nursery index. --- .../Controllers/V2/Nurseries/AdminIndexNurseriesController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/V2/Nurseries/AdminIndexNurseriesController.php b/app/Http/Controllers/V2/Nurseries/AdminIndexNurseriesController.php index 05a6167b9..bdd340248 100644 --- a/app/Http/Controllers/V2/Nurseries/AdminIndexNurseriesController.php +++ b/app/Http/Controllers/V2/Nurseries/AdminIndexNurseriesController.php @@ -29,7 +29,7 @@ public function __invoke(Request $request): NurseriesCollection AllowedFilter::scope('country'), AllowedFilter::scope('organisation_uuid', 'organisationUuid'), AllowedFilter::scope('project_uuid', 'projectUuid'), - AllowedFilter::exact('framework', 'framework_key'), + AllowedFilter::exact('framework_key'), AllowedFilter::exact('status'), AllowedFilter::exact('update_request_status'), ]); From ea57632456955d0643ba130d0a85aca75d3da8a2 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Fri, 24 May 2024 10:42:12 -0700 Subject: [PATCH 10/12] [TM-929] Update test to match filter change. --- tests/V2/Nurseries/AdminIndexNurseriesControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/V2/Nurseries/AdminIndexNurseriesControllerTest.php b/tests/V2/Nurseries/AdminIndexNurseriesControllerTest.php index b24bf9f5d..86f76aef0 100644 --- a/tests/V2/Nurseries/AdminIndexNurseriesControllerTest.php +++ b/tests/V2/Nurseries/AdminIndexNurseriesControllerTest.php @@ -61,7 +61,7 @@ public function test_nursery_index_has_required_filters() 'country', 'organisation_uuid', 'project_uuid', - 'framework', + 'framework_key', ]; foreach ($filters as $filter) { From 27bc1f9f0553a80b1271517275af866cfb3d437a Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Tue, 28 May 2024 14:38:11 -0700 Subject: [PATCH 11/12] [TM-950] Allow the collection for linked fields to get stored in the DB form question. --- app/Http/Requests/V2/Forms/StoreFormRequest.php | 2 ++ app/Http/Requests/V2/Forms/UpdateFormRequest.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/Http/Requests/V2/Forms/StoreFormRequest.php b/app/Http/Requests/V2/Forms/StoreFormRequest.php index 586bdaf53..b5d2791fa 100644 --- a/app/Http/Requests/V2/Forms/StoreFormRequest.php +++ b/app/Http/Requests/V2/Forms/StoreFormRequest.php @@ -34,6 +34,7 @@ public function rules() 'form_sections.*.form_questions' => ['sometimes', 'array'], 'form_sections.*.form_questions.*.linked_field_key' => ['sometimes', 'nullable', 'string'], + 'form_sections.*.form_questions.*.collection' => ['sometimes', 'nullable', 'string'], 'form_sections.*.form_questions.*.label' => ['required', 'string'], 'form_sections.*.form_questions.*.input_type' => ['sometimes', 'nullable'], 'form_sections.*.form_questions.*.name' => ['sometimes', 'nullable'], @@ -58,6 +59,7 @@ public function rules() 'form_sections.*.form_questions.*.child_form_questions' => ['sometimes'], 'form_sections.*.form_questions.*.child_form_questions.*.linked_field_key' => ['sometimes', 'required', 'string'], + 'form_sections.*.form_questions.*.child_form_questions.*.collection' => ['sometimes', 'nullable', 'string'], 'form_sections.*.form_questions.*.child_form_questions.*.label' => ['sometimes', 'required', 'string'], 'form_sections.*.form_questions.*.child_form_questions.*.name' => ['sometimes', 'nullable'], 'form_sections.*.form_questions.*.child_form_questions.*.input_type' => ['sometimes', 'nullable'], diff --git a/app/Http/Requests/V2/Forms/UpdateFormRequest.php b/app/Http/Requests/V2/Forms/UpdateFormRequest.php index 6e02393a9..c7dea033a 100644 --- a/app/Http/Requests/V2/Forms/UpdateFormRequest.php +++ b/app/Http/Requests/V2/Forms/UpdateFormRequest.php @@ -34,6 +34,7 @@ public function rules() 'form_sections.*.form_questions' => ['sometimes', 'nullable', 'array'], 'form_sections.*.form_questions.*.linked_field_key' => ['sometimes', 'nullable', 'string'], + 'form_sections.*.form_questions.*.collection' => ['sometimes', 'nullable', 'string'], 'form_sections.*.form_questions.*.label' => ['sometimes', 'nullable', 'string'], 'form_sections.*.form_questions.*.uuid' => ['sometimes', 'nullable', 'string'], 'form_sections.*.form_questions.*.name' => ['sometimes', 'nullable'], @@ -63,6 +64,7 @@ public function rules() 'form_sections.*.form_questions.*.child_form_questions' => ['sometimes', 'nullable'], 'form_sections.*.form_questions.*.child_form_questions.*.uuid' => ['sometimes', 'nullable', 'string'], 'form_sections.*.form_questions.*.child_form_questions.*.linked_field_key' => ['sometimes', 'nullable', 'string'], + 'form_sections.*.form_questions.*.child_form_questions.*.collection' => ['sometimes', 'nullable', 'string'], 'form_sections.*.form_questions.*.child_form_questions.*.label' => ['sometimes', 'nullable', 'string'], 'form_sections.*.form_questions.*.child_form_questions.*.name' => ['sometimes', 'nullable'], 'form_sections.*.form_questions.*.child_form_questions.*.input_type' => ['sometimes', 'nullable'], From c014a3c2712a92a7febf259a8af764d9533744e0 Mon Sep 17 00:00:00 2001 From: Nathan Curtis Date: Wed, 29 May 2024 09:27:40 -0700 Subject: [PATCH 12/12] [TM-938] Send both the old count and the new count to the client as separate fields. --- .../Resources/V2/Projects/ProjectResource.php | 2 ++ app/Http/Resources/V2/Sites/SiteResource.php | 2 ++ app/Models/V2/Projects/Project.php | 15 +++++++++++++++ app/Models/V2/Sites/Site.php | 11 +++++++++++ 4 files changed, 30 insertions(+) diff --git a/app/Http/Resources/V2/Projects/ProjectResource.php b/app/Http/Resources/V2/Projects/ProjectResource.php index 4417e7d5c..eae518e46 100644 --- a/app/Http/Resources/V2/Projects/ProjectResource.php +++ b/app/Http/Resources/V2/Projects/ProjectResource.php @@ -52,6 +52,8 @@ public function toArray($request) 'seeds_planted_count' => $this->seeds_planted_count, 'regenerated_trees_count' => $this->regenerated_trees_count, 'workday_count' => $this->workday_count, + // Temporary until we have bulk import completed. + 'self_reported_workday_count' => $this->self_reported_workday_count, 'total_jobs_created' => $this->total_jobs_created, 'total_sites' => $this->total_sites, 'total_nurseries' => $this->total_nurseries, diff --git a/app/Http/Resources/V2/Sites/SiteResource.php b/app/Http/Resources/V2/Sites/SiteResource.php index eb98d950f..88e2bfff3 100644 --- a/app/Http/Resources/V2/Sites/SiteResource.php +++ b/app/Http/Resources/V2/Sites/SiteResource.php @@ -48,6 +48,8 @@ public function toArray($request) 'site_reports_total' => $this->site_reports_total, 'overdue_site_reports_total' => $this->overdue_site_reports_total, 'workday_count' => $this->workday_count, + // Temporary until we have bulk import completed. + 'self_reported_workday_count' => $this->self_reported_workday_count, 'trees_planted_count' => $this->trees_planted_count, 'regenerated_trees_count' => $this->regenerated_trees_count, 'migrated' => ! empty($this->old_model), diff --git a/app/Models/V2/Projects/Project.php b/app/Models/V2/Projects/Project.php index c1d76cb03..c946f5722 100644 --- a/app/Models/V2/Projects/Project.php +++ b/app/Models/V2/Projects/Project.php @@ -36,6 +36,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; @@ -372,6 +373,20 @@ public function getWorkdayCountAttribute(): int )->gender()->sum('amount') ?? 0; } + public function getSelfReportedWorkdayCountAttribute(): int + { + $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 { $ftTotal = ProjectReport::where('project_id', $this->id) diff --git a/app/Models/V2/Sites/Site.php b/app/Models/V2/Sites/Site.php index d3dda28d5..96b69b828 100644 --- a/app/Models/V2/Sites/Site.php +++ b/app/Models/V2/Sites/Site.php @@ -31,6 +31,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; @@ -311,6 +312,16 @@ public function getWorkdayCountAttribute(): int )->gender()->sum('amount') ?? 0; } + public function getSelfReportedWorkdayCountAttribute(): int + { + $totals = $this->reports()->hasBeenSubmitted()->get([ + DB::raw('sum(`workdays_volunteer`) as volunteer'), + DB::raw('sum(`workdays_paid`) as paid'), + ])->first(); + + return $totals?->paid + $totals?->volunteer; + } + public function getFrameworkUuidAttribute(): ?string { return $this->framework ? $this->framework->uuid : null;