diff --git a/app/Actions/ExportReport.php b/app/Actions/ExportReport.php index f212633b..827edfba 100644 --- a/app/Actions/ExportReport.php +++ b/app/Actions/ExportReport.php @@ -21,6 +21,8 @@ class ExportReport extends Action protected bool | null $showMissingValues = false; + protected bool | null $addCasesInMonitoring = false; + protected function setUp(): void { parent::setUp(); @@ -58,6 +60,13 @@ public function setShowMissingValues(?bool $showMissingValues): self return $this; } + public function setAddCasesInMonitoring(?bool $addCasesInMonitoring): self + { + $this->addCasesInMonitoring = $addCasesInMonitoring; + + return $this; + } + public function generateExport(): BinaryFileResponse { $service = new BeneficiariesV2(); @@ -65,6 +74,7 @@ public function generateExport(): BinaryFileResponse ->setStartDate($this->startDate) ->setEndDate($this->endDate) ->setShowMissingValue($this->showMissingValues) + ->setAddCasesInMonitoring($this->addCasesInMonitoring) ->composeReport(); $fileName = \sprintf('%s_%s_%s.xlsx', $this->startDate, $this->endDate, $this->reportType->value); diff --git a/app/Concerns/HasPermissions.php b/app/Concerns/HasPermissions.php index 857fc556..03707e0c 100644 --- a/app/Concerns/HasPermissions.php +++ b/app/Concerns/HasPermissions.php @@ -66,7 +66,7 @@ public function hasAccessToNomenclature(): bool return (bool) $this->permissions?->admin_permissions->contains(AdminPermission::CAN_CHANGE_NOMENCLATURE); } - public function hasAccessToCommunity(): bool + public function hasAccessToCommunity() { if ($this->isAdmin()) { return true; @@ -87,4 +87,13 @@ public function canSearchBeneficiary(): bool return $this->permissions?->case_permissions->contains(CasePermission::CAN_SEARCH_AND_COPY_CASES_IN_ALL_CENTERS); } + + public function hasAccessToReports(): bool + { + if ($this->isNgoAdmin()) { + return true; + } + + return (bool) $this->permissions?->case_permissions->contains(CasePermission::HAS_ACCESS_TO_STATISTICS); + } } diff --git a/app/Concerns/LogsActivityOptions.php b/app/Concerns/LogsActivityOptions.php index be13ad7e..5ebaa60f 100644 --- a/app/Concerns/LogsActivityOptions.php +++ b/app/Concerns/LogsActivityOptions.php @@ -5,6 +5,7 @@ namespace App\Concerns; use App\Models\Activity; +use Illuminate\Database\Eloquent\Relations\HasMany; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\LogsActivity; @@ -31,4 +32,10 @@ public function tapActivity(Activity $activity, string $eventName) $activity->event = $activity->subject_type; $activity->subject()->associate($this->beneficiary); } + + public function activity(): HasMany + { + return $this->hasMany(Activity::class, 'subject_id') + ->where('subject_type', 'beneficiary'); + } } diff --git a/app/Filament/Organizations/Pages/ReportsPage.php b/app/Filament/Organizations/Pages/ReportsPage.php index 998fd995..f759c997 100644 --- a/app/Filament/Organizations/Pages/ReportsPage.php +++ b/app/Filament/Organizations/Pages/ReportsPage.php @@ -8,8 +8,12 @@ use App\Enums\ReportType; use App\Forms\Components\ReportTable; use Filament\Forms; +use Filament\Forms\Components\Checkbox; +use Filament\Forms\Components\DatePicker; +use Filament\Forms\Components\Select; use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Form; +use Filament\Forms\Get; use Filament\Infolists\Components\Section; use Filament\Infolists\Contracts\HasInfolists; use Filament\Infolists\Infolist; @@ -17,6 +21,7 @@ use Filament\Pages\Page; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Contracts\View\View; +use Illuminate\Support\HtmlString; class ReportsPage extends Page implements Forms\Contracts\HasForms, HasInfolists { @@ -37,6 +42,13 @@ class ReportsPage extends Page implements Forms\Contracts\HasForms, HasInfolists public $show_missing_values; + public $add_cases_in_monitoring; + + public static function canAccess(): bool + { + return auth()->user()->hasAccessToReports(); + } + public static function getNavigationGroup(): ?string { return __('navigation.statistics._group'); @@ -69,22 +81,39 @@ protected function getFormSchema(): array Forms\Components\Section::make() ->columns(4) ->schema([ - Forms\Components\Select::make('report_type') + Select::make('report_type') ->key('report_type') ->label(__('report.labels.report_type')) ->columnSpan(2) ->options(ReportType::options()) ->searchable(), - Forms\Components\DatePicker::make('start_date') + DatePicker::make('start_date') ->label(__('report.labels.start_date')) - ->default(now()->startOfMonth()), + ->default(now()->startOfMonth()) + ->maxDate(fn (Get $get) => $get('end_date') ? debug($get('end_date')) : now()) + ->live(), - Forms\Components\DatePicker::make('end_date') + DatePicker::make('end_date') ->label(__('report.labels.end_date')) - ->default(now()), + ->default(now()) + ->minDate(fn (Get $get) => $get('start_date') ?? null) + ->maxDate(now()) + ->live(), + + Checkbox::make('add_cases_in_monitoring') + ->label( + new HtmlString( + \sprintf( + '%s', + __('report.helpers.add_cases_in_monitoring'), + __('report.labels.add_cases_in_monitoring'), + ) + ) + ) + ->columnSpan(2), - Forms\Components\Checkbox::make('show_missing_values') + Checkbox::make('show_missing_values') ->label(__('report.labels.show_missing_values')) ->default(true) ->columnSpan(2), @@ -107,7 +136,8 @@ public function infolist(Infolist $infolist): Infolist ->setReportType($this->report_type) ->setStartDate($this->start_date) ->setEndDate($this->end_date) - ->setShowMissingValues($this->show_missing_values), + ->setShowMissingValues($this->show_missing_values) + ->setAddCasesInMonitoring($this->add_cases_in_monitoring), ]) ->schema([ $this->reportTable(), @@ -121,7 +151,8 @@ public function reportTable(): ReportTable ->setReportType($this->report_type ? ReportType::tryFrom($this->report_type) : null) ->setStartDate($this->start_date) ->setEndDate($this->end_date) - ->setShowMissingValue($this->show_missing_values); + ->setShowMissingValue($this->show_missing_values) + ->setAddCasesInMonitoring($this->add_cases_in_monitoring); } public function render(): View diff --git a/app/Forms/Components/ReportTable.php b/app/Forms/Components/ReportTable.php index 1effe928..f126f3c5 100644 --- a/app/Forms/Components/ReportTable.php +++ b/app/Forms/Components/ReportTable.php @@ -5,7 +5,6 @@ namespace App\Forms\Components; use App\Enums\ReportType; -use App\Services\Reports\Beneficiaries; use App\Services\Reports\BeneficiariesV2; use Filament\Infolists\Components\Component; use Illuminate\Support\Collection; @@ -14,7 +13,7 @@ class ReportTable extends Component { protected string $view = 'forms.components.report-table'; - protected Beneficiaries | BeneficiariesV2 $reportService; + protected BeneficiariesV2 $reportService; protected ReportType | null $reportType = null; @@ -24,6 +23,8 @@ class ReportTable extends Component protected bool | null $showMissingValues = false; + protected bool | null $addCasesInMonitoring = false; + public static function make(string | null $id = null): static { $static = app(static::class, ['id' => $id]); @@ -36,7 +37,6 @@ protected function setUp(): void { parent::setUp(); $this->reportService = new BeneficiariesV2(); -// $this->reportService = new Beneficiaries(); } public function setReportType(ReportType | string | null $reportType): self @@ -71,14 +71,17 @@ public function setShowMissingValue(?bool $showMissingValue): self return $this; } - public function composeReport(): void + public function setAddCasesInMonitoring(?bool $addCasesInMonitoring): self { - $this->reportService->composeReport(); + $this->addCasesInMonitoring = $addCasesInMonitoring; + $this->reportService->setAddCasesInMonitoring($addCasesInMonitoring); + + return $this; } - public function getReportType(): ?ReportType + public function composeReport(): void { - return $this->reportService->getReportType(); + $this->reportService->composeReport(); } public function getReportData(): Collection diff --git a/app/Services/Reports/BeneficiariesReports/BaseGenerator.php b/app/Services/Reports/BeneficiariesReports/BaseGenerator.php index 9d236c41..dd082da8 100644 --- a/app/Services/Reports/BeneficiariesReports/BaseGenerator.php +++ b/app/Services/Reports/BeneficiariesReports/BaseGenerator.php @@ -4,38 +4,45 @@ namespace App\Services\Reports\BeneficiariesReports; +use App\Enums\ActivityDescription; +use App\Enums\CaseStatus; use App\Models\Beneficiary; +use Carbon\Carbon; +use DB; +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Query\Builder; use Illuminate\Support\Collection; abstract class BaseGenerator { - protected string | null $startDate; + protected string | null $startDate = null; protected string | null $endDate = null; protected bool | null $showMissingValues = false; + protected bool | null $addCasesInMonitoring = false; + protected $query; public function __construct() { $this->query = Beneficiary::query() - ->leftJoin('close_files', 'beneficiaries.id', '=', 'close_files.beneficiary_id') - ->toBase() ->selectRaw('COUNT(*) as total_cases'); } public function setStartDate(?string $startDate): self { - $this->startDate = $startDate; + $this->startDate = $startDate ? Carbon::parse($startDate)->startOfDay()->format('Y-m-d H:i:s') : null; return $this; } public function setEndDate(?string $endDate): self { - $this->endDate = $endDate; + $this->endDate = $endDate ? + Carbon::parse($endDate)->endOfDay()->format('Y-m-d H:i:s') : + Carbon::now()->endOfDay()->format('Y-m-d H:i:s'); return $this; } @@ -47,6 +54,13 @@ public function setShowMissingValues(?bool $showMissingValues): self return $this; } + public function setAddCasesInMonitoring(?bool $addCasesInMonitoring): self + { + $this->addCasesInMonitoring = $addCasesInMonitoring; + + return $this; + } + public function getReportData(): Collection { $this->setSelectedFields(); @@ -56,6 +70,7 @@ public function getReportData(): Collection ->groupBy($this->getGroupBy()); return $this->query + ->toBase() ->get(); } @@ -89,16 +104,70 @@ public function addConditions(): void } } - if ($this->endDate) { - $this->query->where('beneficiaries.created_at', '<=', $this->endDate . ' 23:59:59'); - } + $this->addDateConditions(); + } - if ($this->startDate) { - $this->query->where( - fn (Builder $query) => $query->where('close_files.date', '>=', $this->startDate) - ->orWhereNull('close_files.date') + public function addDateConditions(): void + { + $this->query + ->when( + $this->startDate, + fn (EloquentBuilder $query) => $query + ->whereHas( + 'activity', + fn (EloquentBuilder $query) => $query + ->where( + fn (EloquentBuilder $query) => $query->whereJsonContains('properties->attributes->status', CaseStatus::ACTIVE->value) + ->when( + $this->addCasesInMonitoring, + fn (EloquentBuilder $query) => $query->orWhereJsonContains('properties->attributes->status', CaseStatus::MONITORED->value) + ) + ) + ->where('created_at', '<=', $this->endDate) + ->whereIn('activity_log.description', [ActivityDescription::CREATED->value, ActivityDescription::UPDATED->value]) + ->whereNotExists( + fn (Builder $subQuery) => $subQuery->select(DB::raw(1)) + ->from('activity_log as sublog') + ->whereColumn('sublog.subject_id', 'activity_log.subject_id') + ->whereIn('sublog.description', [ + ActivityDescription::CREATED->value, + ActivityDescription::UPDATED->value, + ]) + ->where('sublog.subject_type', 'beneficiary') + ->whereColumn('sublog.created_at', '>', 'activity_log.created_at') + ->whereJsonContainsKey('properties->attributes->status') + ->where('sublog.created_at', '<=', $this->endDate) + ) + ) + ->orWhereHas( + 'activity', + fn (EloquentBuilder $query) => $query->whereJsonContains('properties->old->status', CaseStatus::ACTIVE->value) + ->when( + $this->addCasesInMonitoring, + fn (EloquentBuilder $query) => $query->orWhereJsonContains('properties->old->status', CaseStatus::MONITORED->value) + ) + ->whereBetween('created_at', [$this->startDate, $this->endDate]) + ) + ) + ->when( + ! $this->startDate, + fn (EloquentBuilder $query) => $query + ->whereHas( + 'activity', + fn (EloquentBuilder $query) => $query + ->where( + fn (EloquentBuilder $query) => $query + ->whereJsonContains('properties->attributes->status', CaseStatus::ACTIVE->value) + ->when( + $this->addCasesInMonitoring, + fn (EloquentBuilder $query) => $query->orWhereJsonContains('properties->attributes->status', CaseStatus::MONITORED->value) + ) + ) + ->whereIn('description', [ActivityDescription::CREATED->value, ActivityDescription::UPDATED->value]) + ->where('created_at', '<=', $this->endDate) + ->where('subject_type', 'beneficiary') + ) ); - } } public function addRelatedTables(): void diff --git a/app/Services/Reports/BeneficiariesV2.php b/app/Services/Reports/BeneficiariesV2.php index f5baa90d..01f239be 100644 --- a/app/Services/Reports/BeneficiariesV2.php +++ b/app/Services/Reports/BeneficiariesV2.php @@ -20,6 +20,8 @@ class BeneficiariesV2 protected bool | null $showMissingValue = false; + protected bool | null $addCasesInMonitoring = false; + public function setReportType(ReportType | string | null $reportType): self { $this->reportType = $reportType; @@ -48,6 +50,13 @@ public function setShowMissingValue(?bool $showMissingValue): self return $this; } + public function setAddCasesInMonitoring(?bool $addCasesInMonitoring): self + { + $this->addCasesInMonitoring = $addCasesInMonitoring; + + return $this; + } + public function composeReport(): void { $generatorClass = str_replace(' ', '', ucwords(str_replace('_', ' ', $this->reportType->value))); @@ -61,7 +70,8 @@ public function composeReport(): void $this->generator ->setStartDate($this->startDate) ->setEndDate($this->endDate) - ->setShowMissingValues($this->showMissingValue); + ->setShowMissingValues($this->showMissingValue) + ->setAddCasesInMonitoring($this->addCasesInMonitoring); } public function getReportData(): Collection diff --git a/lang/ro/report.php b/lang/ro/report.php index b57b5229..3ff177c2 100644 --- a/lang/ro/report.php +++ b/lang/ro/report.php @@ -11,6 +11,7 @@ 'end_date' => 'Dată final raportare', 'show_missing_values' => 'Afișează și datele lipsă (missing values) în tabel', 'total' => 'Total cazuri', + 'add_cases_in_monitoring' => 'Include și cazurile în monitorizare', ], 'table_heading' => [ @@ -93,4 +94,8 @@ 'generate' => 'Generează raport', 'export' => 'Exportă date', ], + + 'helpers' => [ + 'add_cases_in_monitoring' => 'Sistemul include în statistici toți beneficiarii care au avut un caz cu status DESCHIS cel puțin o zi în intervalul de referință (definit de datele de început/final raportare). Bifarea acestei opțiuni va include și cazurile cu status ÎN MONITORIZARE în statisticile raportate.', + ], ];