Skip to content

Commit

Permalink
Merge pull request #199 from wri/epic/TM-838-workdays
Browse files Browse the repository at this point in the history
[TM-838] Workdays data / UI refactor
  • Loading branch information
roguenet authored May 15, 2024
2 parents cd6b3ec + adc6bd7 commit 1f9a717
Show file tree
Hide file tree
Showing 32 changed files with 938 additions and 449 deletions.
138 changes: 138 additions & 0 deletions app/Console/Commands/OneOff/MigrateWorkdayData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

namespace App\Console\Commands\OneOff;

use App\Models\V2\Workdays\Workday;
use App\Models\V2\Workdays\WorkdayDemographic;
use Illuminate\Console\Command;
use Illuminate\Support\Str;

class MigrateWorkdayData extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'one-off:migrate-workday-data';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Updates Workday data to use the new workday_demographics table';

private const DEMOGRAPHICS = [
WorkdayDemographic::GENDER,
WorkdayDemographic::AGE,
WorkdayDemographic::ETHNICITY,
];

private const SUBTYPE_NULL = 'subtype-null';
private const NAME_NULL = 'name-null';

/**
* Execute the console command.
*/
public function handle()
{
$entityTypes = Workday::select('workdayable_type')->distinct()->pluck('workdayable_type');
foreach ($entityTypes as $entityType) {
$entityIds = Workday::where('workdayable_type', $entityType)
->select('workdayable_id')
->distinct()
->pluck('workdayable_id');
$count = $entityIds->count();
$shortName = explode_pop('\\', $entityType);
echo "Processing $shortName: $count records\n";

foreach ($entityIds as $entityId) {
$this->updateEntityWorkdays($entityType, $entityId);
}
}
}

private function updateEntityWorkdays(string $entityType, int $entityId): void
{
$workdayCollections = Workday::where(['workdayable_type' => $entityType, 'workdayable_id' => $entityId])
->get()
->reduce(function (array $carry, Workday $workday) {
$carry[$workday['collection']][] = $workday;

return $carry;
}, []);

foreach ($workdayCollections as $collection => $workdays) {
$mapping = $this->mapWorkdayCollection($workdays);
$framework_key = $workdays[0]->framework_key;

$workday = Workday::create([
'workdayable_type' => $entityType,
'workdayable_id' => $entityId,
'framework_key' => $framework_key,
'collection' => $collection,
]);
foreach ($mapping as $demographic => $subTypes) {
foreach ($subTypes as $subType => $names) {
foreach ($names as $name => $amount) {
WorkdayDemographic::create([
'workday_id' => $workday->id,
'type' => $demographic,
'subtype' => $subType == self::SUBTYPE_NULL ? null : $subType,
'name' => $name == self::NAME_NULL ? null : $name,
'amount' => $amount,
]);
}
}
}

$workdayIds = collect($workdays)->map(fn ($workday) => $workday->id)->all();
Workday::whereIn('id', $workdayIds)->update(['migrated_to_demographics' => true]);
Workday::whereIn('id', $workdayIds)->delete();
}
}

private function mapWorkdayCollection(array $workdays): array
{
$demographics = [];
foreach (self::DEMOGRAPHICS as $demographic) {
foreach ($workdays as $workday) {
$subType = $this->getSubtype($demographic, $workday);
$name = match ($workday[$demographic]) {
null, 'gender-undefined', 'age-undefined', 'decline-to-specify' => 'unknown',
default => $workday[$demographic],
};
if ($subType == 'unknown' && strcasecmp($name, 'unknown') == 0) {
// We only get an unknown subtype when we're working on ethnicity. If the value is also unknown in
// this case, we want to leave it null.
$name = self::NAME_NULL;
}

$current = data_get($demographics, "$demographic.$subType.$name");
data_set($demographics, "$demographic.$subType.$name", $current + $workday->amount);
}
}

return $demographics;
}

private function getSubtype(string $demographic, Workday $workday): string
{
if ($demographic != WorkdayDemographic::ETHNICITY) {
return self::SUBTYPE_NULL;
}

if ($workday->indigeneity != null && $workday->indigeneity != 'decline to specify') {
return $workday->indigeneity;
}

if (Str::startsWith($workday->ethnicity, 'indigenous')) {
return 'indigenous';
} elseif (Str::startsWith($workday->ethnicity, 'other')) {
return 'other';
}

return 'unknown';
}
}
93 changes: 93 additions & 0 deletions app/Console/Commands/ReportWorkdayDiscrepancies.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace App\Console\Commands;

use App\Models\V2\Projects\ProjectReport;
use App\Models\V2\Sites\SiteReport;
use App\Models\V2\Workdays\Workday;
use Illuminate\Console\Command;

class ReportWorkdayDiscrepancies extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'report-workday-discrepancies';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Reports on project and site reports that have a difference between aggregated and disaggregated workday numbers';

private const PROPERTIES = [
ProjectReport::class => [
'paid' => [
Workday::COLLECTION_PROJECT_PAID_NURSERY_OPERATIONS,
Workday::COLLECTION_PROJECT_PAID_PROJECT_MANAGEMENT,
Workday::COLLECTION_PROJECT_PAID_OTHER,
],
'volunteer' => [
Workday::COLLECTION_PROJECT_VOLUNTEER_NURSERY_OPERATIONS,
Workday::COLLECTION_PROJECT_VOLUNTEER_PROJECT_MANAGEMENT,
Workday::COLLECTION_PROJECT_VOLUNTEER_OTHER,
],
],
SiteReport::class => [
'paid' => [
Workday::COLLECTION_SITE_PAID_SITE_ESTABLISHMENT,
Workday::COLLECTION_SITE_PAID_PLANTING,
Workday::COLLECTION_SITE_PAID_SITE_MAINTENANCE,
Workday::COLLECTION_SITE_PAID_SITE_MONITORING,
Workday::COLLECTION_SITE_PAID_OTHER,
],
'volunteer' => [
Workday::COLLECTION_SITE_VOLUNTEER_SITE_ESTABLISHMENT,
Workday::COLLECTION_SITE_VOLUNTEER_PLANTING,
Workday::COLLECTION_SITE_VOLUNTEER_SITE_MAINTENANCE,
Workday::COLLECTION_SITE_VOLUNTEER_SITE_MONITORING,
Workday::COLLECTION_SITE_VOLUNTEER_OTHER,
],
],
];

/**
* Execute the console command.
*/
public function handle()
{
echo "Model Type,Model UUID,Aggregate Paid Total,Disaggregate Paid Total,Aggregate Volunteer Total,Disaggregate Volunteer Total\n";
foreach (self::PROPERTIES as $model => $propertySets) {
$model::where('status', 'approved')->chunkById(
100,
function ($reports) use ($propertySets) {
foreach ($reports as $report) {
$aggregate_paid = (int)$report->workdays_paid;
$aggregate_volunteer = (int)$report->workdays_volunteer;

$modelType = get_class($report);
$query = Workday::where([
'workdayable_type' => $modelType,
'workdayable_id' => $report->id,
]);
if ($query->count() == 0) {
// Skip reports that have no associated workday rows.
continue;
}

$disaggregate_paid = (int)(clone $query)->whereIn('collection', $propertySets['paid'])->sum('amount');
$disaggregate_volunteer = (int)(clone $query)->whereIn('collection', $propertySets['volunteer'])->sum('amount');

if ($aggregate_paid != $disaggregate_paid || $aggregate_volunteer != $disaggregate_volunteer) {
$shortType = explode_pop('\\', $modelType);
echo "$shortType,$report->uuid,$aggregate_paid,$disaggregate_paid,$aggregate_volunteer,$disaggregate_volunteer\n";
}
}
}
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,20 @@

use App\Http\Controllers\Controller;
use App\Http\Resources\V2\TreeSpecies\TreeSpeciesCollection;
use App\Models\V2\Nurseries\Nursery;
use App\Models\V2\Nurseries\NurseryReport;
use App\Models\V2\Projects\Project;
use App\Models\V2\Projects\ProjectReport;
use App\Models\V2\Sites\Site;
use App\Models\V2\Sites\SiteReport;
use App\Models\V2\EntityModel;
use App\Models\V2\TreeSpecies\TreeSpecies;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class GetTreeSpeciesForEntityController extends Controller
{
public function __invoke(Request $request, string $entity, string $uuid)
public function __invoke(Request $request, EntityModel $entity)
{
$model = $this->getModel($entity);

if (is_null($model)) {
return new JsonResponse($entity . ' is not a valid entity key', 422);
}

$object = $model::isUuid($uuid)->first();

$this->authorize('read', $object);

if (is_null($object)) {
return new JsonResponse($entity . ' record not found', 404);
}
$this->authorize('read', $entity);

$query = TreeSpecies::query()
->where('speciesable_type', $model)
->where('speciesable_id', $object->id);
->where('speciesable_type', get_class($entity))
->where('speciesable_id', $entity->id);

return new TreeSpeciesCollection($query->paginate());
}

private function getModel(string $entity)
{
$model = null;

switch ($entity) {
case 'project':
$model = Project::class;

break;
case 'site':
$model = Site::class;

break;
case 'nursery':
$model = Nursery::class;

break;
case 'project-report':
$model = ProjectReport::class;

break;
case 'site-report':
$model = SiteReport::class;

break;
case 'nursery-report':
$model = NurseryReport::class;

break;
}

return $model;
}
}
Loading

0 comments on commit 1f9a717

Please sign in to comment.