Skip to content

Commit

Permalink
Merge pull request #1569 from younginnovations/1542-review-data-compl…
Browse files Browse the repository at this point in the history
…eteness-alert-for-activity-result-element

Review: 1542-review-data-completeness-alert-for-activity-result-element
  • Loading branch information
Sanilblank authored Sep 23, 2024
2 parents b82c232 + 98d5c2e commit d765933
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 107 deletions.
76 changes: 76 additions & 0 deletions app/Console/Commands/RefreshActivityElementCompleteness.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\IATI\Models\Activity\Activity;
use App\IATI\Services\ElementCompleteService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Facades\DB;

/**
* class RefreshActivityElementCompleteness.
*/
class RefreshActivityElementCompleteness extends Command
{
/**
* @var string
*/
protected $signature = 'command:RefreshActivityElementCompleteness';

/**
* Will refresh activity->element_status field.
*
* @var string
*/
protected $description = ' Will refresh activity->element_status field.';

/**
* Execute the console command.
*
* @return void
* @throws BindingResolutionException
*/
public function handle(): void
{
/** @var ElementCompleteService $elementCompleteService */
$elementCompleteService = app()->make(ElementCompleteService::class);

Activity::with('transactions', 'results.indicators.periods')->latest()->chunk(100, function ($activities) use ($elementCompleteService) {
DB::beginTransaction();
try {
foreach ($activities as $activity) {
$this->info("Started for activity: $activity->id");

$activityElementNames = $activity->getAttributes();
$elementStatus = [];

foreach ($activityElementNames as $element => $value) {
$methodName = dashesToCamelCase('is_' . $element . '_element_completed');

if (method_exists($elementCompleteService, $methodName)) {
$elementStatus[$element] = $elementCompleteService->$methodName($activity);
}
}

$elementStatus['result'] = $elementCompleteService->isResultElementCompleted($activity);
$elementStatus['transactions'] = $elementCompleteService->isTransactionsElementCompleted($activity);

$activity->timestamps = false;
$activity->updateQuietly(['element_status' => $elementStatus]);

$this->info("Completed for activity: $activity->id");
$this->info('---------------------------------------');
}

DB::commit();
} catch (Exception $e) {
DB::rollBack();
$this->error($e->getMessage());
}
});
}
}
70 changes: 35 additions & 35 deletions app/IATI/Data/elementJsonSchema.json

Large diffs are not rendered by default.

84 changes: 48 additions & 36 deletions app/IATI/Services/ElementCompleteService.php
Original file line number Diff line number Diff line change
Expand Up @@ -804,37 +804,42 @@ public function getFormattedResults($activity): array
$indicatorData = [];
$periodData = [];

if (!empty($results)) {
foreach ($results as $resultKey => $result) {
$resultData[] = $result['result'];
$indicators = $result['indicators'];
$resultReference = Arr::get($result['result'], 'reference');

if (!empty($indicators)) {
foreach ($indicators as $indicatorKey => $indicator) {
$indicatorValue = $indicator['indicator'];

if (is_array_value_empty(Arr::get($indicatorValue, 'reference'))) {
$indicatorValue['reference'] = $resultReference;
} else {
$resultLastKey = array_key_last($resultData);
$resultData[$resultLastKey]['reference'] = Arr::get($indicatorValue, 'reference');
}
$indicatorData[] = $indicatorValue;
$periods = $indicator['periods'];

if (!empty($periods)) {
$indicatorData[$indicatorKey] = $indicatorValue;

foreach ($periods as $period) {
$periodData[] = $period['period'];
}
} else {
$resultData = [];
}
}
foreach ($results as $result) {
$resultId = $result['id'];
$resultIndicators = Arr::get($result, 'indicators', []);

if (empty($resultIndicators)) {
/*
* Indicator is mandatory.
* Setting $resultData[i] to empty array in-case indicator doesn't exist because:
* This is equivalent to doing an early return, no need to continue preparing indicator data or for this result.
* Checking if $resultData contains an empty [] in it will give us result completion faster than calculating if indicator for said result is complete
*/
$resultData[$resultId] = [];
continue;
}

$resultData[$resultId] = Arr::get($result, 'result', []);
$resultReference = Arr::get($resultData[$resultId], 'reference');

foreach ($resultIndicators as $indicator) {
$indicatorId = $indicator['id'];
$indicatorId = "$resultId.$indicatorId";
$indicatorValue = Arr::get($indicator, 'indicator', []);

if (is_array_value_empty(Arr::get($indicatorValue, 'reference'))) {
$indicatorValue['reference'] = $resultReference;
} else {
$resultData = [];
$resultData[$resultId]['reference'] = Arr::get($indicatorValue, 'reference');
}

$indicatorData[$indicatorId] = $indicatorValue;
$periods = Arr::get($indicator, 'periods', []);

if (!empty($periods)) {
foreach ($periods as $period) {
$periodData[] = Arr::get($period, 'period', []);
}
}
}
}
Expand All @@ -855,15 +860,22 @@ public function isResultElementCompleted($activity): bool
{
[$resultData, $indicatorData, $periodData] = $this->getFormattedResults($activity);

if (
(is_variable_null($periodData) || !$this->isPeriodElementCompleted($periodData)) || !$this->isResultElementDataCompleted($resultData)
|| (is_variable_null($indicatorData) || !$this->isIndicatorElementCompleted($indicatorData))
|| (is_variable_null($resultData))
) {
if (is_variable_null($resultData) || count($resultData) < 1) {
return false;
}

return true;
$periodExists = is_array($periodData) && count($periodData) > 0;
$indicatorExists = is_array($indicatorData) && count($indicatorData) > 0;

if ($periodExists) {
return $this->isPeriodElementCompleted($periodData) && $this->isIndicatorElementCompleted($indicatorData) && $this->isResultElementDataCompleted($resultData);
}

if ($indicatorExists) {
return $this->isIndicatorElementCompleted($indicatorData) && $this->isResultElementDataCompleted($resultData);
}

return false;
}

/**
Expand Down
29 changes: 27 additions & 2 deletions app/IATI/Services/ImportActivity/ImportXmlService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

namespace App\IATI\Services\ImportActivity;

use App\IATI\Models\Activity\Activity;
use App\IATI\Repositories\Activity\ActivityRepository;
use App\IATI\Repositories\Activity\IndicatorRepository;
use App\IATI\Repositories\Activity\PeriodRepository;
use App\IATI\Repositories\Activity\ResultRepository;
use App\IATI\Repositories\Activity\TransactionRepository;
use App\IATI\Repositories\Import\ImportActivityErrorRepository;
use App\IATI\Services\ElementCompleteService;
use App\IATI\Traits\FillDefaultValuesTrait;
use App\XmlImporter\Events\XmlWasUploaded;
use App\XmlImporter\Foundation\Support\Providers\XmlServiceProvider;
Expand All @@ -19,7 +21,6 @@
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;

Expand Down Expand Up @@ -100,6 +101,11 @@ class ImportXmlService
*/
protected ImportActivityErrorRepository $importActivityErrorRepo;

/**
* @var ElementCompleteService
*/
protected ElementCompleteService $elementCompleteService;

/**
* XmlImportManager constructor.
*
Expand All @@ -114,6 +120,7 @@ class ImportXmlService
* @param LoggerInterface $logger
* @param Filesystem $filesystem
* @param XmlService $xmlService
* @param ElementCompleteService $elementCompleteService
*/
public function __construct(
XmlServiceProvider $xmlServiceProvider,
Expand All @@ -126,7 +133,8 @@ public function __construct(
XmlProcessor $xmlProcessor,
LoggerInterface $logger,
Filesystem $filesystem,
XmlService $xmlService
XmlService $xmlService,
ElementCompleteService $elementCompleteService,
) {
$this->xmlServiceProvider = $xmlServiceProvider;
$this->xmlProcessor = $xmlProcessor;
Expand All @@ -142,6 +150,7 @@ public function __construct(
$this->xml_file_storage_path = env('XML_FILE_STORAGE_PATH', 'XmlImporter/file');
$this->xml_data_storage_path = env('XML_DATA_STORAGE_PATH', 'XmlImporter/tmp');
$this->csv_data_storage_path = env('CSV_DATA_STORAGE_PATH', 'CsvImporter/tmp');
$this->elementCompleteService = $elementCompleteService;
}

/**
Expand Down Expand Up @@ -204,6 +213,7 @@ public function create($activities): bool
$this->resultRepository->deleteResult($oldActivity->id);
$this->saveTransactions(Arr::get($activityData, 'transactions'), $oldActivity->id, $defaultFieldValues);
$this->saveResults(Arr::get($activityData, 'result'), $oldActivity->id, $defaultFieldValues);
$this->refreshActivityElementStatusForResult($oldActivity);

if (!empty($activity['errors'])) {
$this->importActivityErrorRepo->updateOrCreateError($oldActivity->id, $activity['errors']);
Expand All @@ -219,6 +229,7 @@ public function create($activities): bool

$this->saveTransactions(Arr::get($activityData, 'transactions'), $storeActivity->id, $defaultFieldValues);
$this->saveResults(Arr::get($activityData, 'result'), $storeActivity->id, $defaultFieldValues);
$this->refreshActivityElementStatusForResult($storeActivity);

if (!empty($activity['errors'])) {
$this->importActivityErrorRepo->updateOrCreateError($storeActivity->id, $activity['errors']);
Expand Down Expand Up @@ -400,4 +411,18 @@ protected function dbIatiIdentifiers($org_id): array
{
return Arr::flatten($this->activityRepository->getActivityIdentifiers($org_id)->toArray());
}

/**
* Since we are doing upsert on results for both creation and update, need to manually check if result is complete.
*
* @throws \JsonException
*/
private function refreshActivityElementStatusForResult(Activity $activity): void
{
$elementStatus = $activity->element_status;
$resultStatus = $this->elementCompleteService->isResultElementCompleted($activity);
$elementStatus['result'] = $resultStatus;

$this->activityRepository->update($activity->id, ['element_status' => $elementStatus]);
}
}
9 changes: 5 additions & 4 deletions resources/assets/js/views/activity/elements/Result.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
'text-crimson-50': !completed,
}"
>
<b class="mr-2 text-base leading-3">.</b>
<span v-if="completed">completed</span>
<span v-else>not completed</span>
<span v-if="!completed">
<b class="mr-2 text-base leading-3">.</b>
not completed
</span>
</div>
</div>
<div class="icons flex items-center">
Expand Down Expand Up @@ -132,7 +133,7 @@
<div>
<NotYet
:link="`/${title}/${result.id}/indicator/create`"
description="You haven't added any indicator yet."
description="You haven't added any Indicator yet. Indicator(s) are required to complete Result."
btn-text="Add new indicator"
/>
</div>
Expand Down
12 changes: 12 additions & 0 deletions resources/assets/js/views/activity/indicators/IndicatorDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@
<a v-smooth-scroll :href="`#${String(r)}`" :class="linkClasses">
<!-- <svg-vue icon="core" class="mr-2 text-base"></svg-vue> -->
{{ r }}
<span
v-if="isMandatoryForIndicator(r)"
class="required-icon px-1"
>
*
</span>
</a>
</li>

Expand Down Expand Up @@ -416,6 +422,11 @@ export default defineComponent({
return positionY.value === 0;
});
const isMandatoryForIndicator = (elementOrAttribute: string) => {
const mandatoryElementOrAttribute = ['measure', 'title'];
return mandatoryElementOrAttribute.includes(elementOrAttribute);
};
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', calcWidth);
Expand All @@ -442,6 +453,7 @@ export default defineComponent({
showSidebar,
istopVisible,
countDocumentLink,
isMandatoryForIndicator,
};
},
});
Expand Down
Loading

0 comments on commit d765933

Please sign in to comment.