Skip to content

Commit 42069e8

Browse files
feat: implement PDF and CSV export functionality for case management
- Added `CaseExportManager` service to handle PDF exports for identity, case info, evaluations, monthly plans, and monitoring reports. - Introduced reusable `case-report-pdf.blade.php` layout for consistent PDF generation. - Integrated CSV export for intervention meetings with proper formatting and translations. - Logged export activities in the `Activity` model for enhanced tracking. - Added feature tests to verify export functionality and content integrity.
1 parent 4220575 commit 42069e8

File tree

21 files changed

+1544
-123
lines changed

21 files changed

+1544
-123
lines changed

app/Filament/Organizations/Resources/Cases/Pages/DetailedEvaluation/ViewCaseDetailedEvaluation.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use App\Models\BeneficiaryPartner;
1919
use App\Models\DetailedEvaluationResult;
2020
use App\Models\MultidisciplinaryEvaluation;
21+
use App\Services\CaseExports\CaseExportManager;
22+
use Filament\Actions\Action;
2123
use Filament\Infolists\Components\RepeatableEntry;
2224
use Filament\Infolists\Components\RepeatableEntry\TableColumn;
2325
use Filament\Infolists\Components\TextEntry;
@@ -28,8 +30,10 @@
2830
use Filament\Schemas\Components\Tabs;
2931
use Filament\Schemas\Components\Tabs\Tab;
3032
use Filament\Schemas\Schema;
33+
use Filament\Support\Icons\Heroicon;
3134
use Illuminate\Contracts\Support\Htmlable;
3235
use Illuminate\Support\Str;
36+
use Symfony\Component\HttpFoundation\StreamedResponse;
3337

3438
class ViewCaseDetailedEvaluation extends ViewRecord
3539
{
@@ -60,6 +64,10 @@ protected function getHeaderActions(): array
6064
return [
6165
BackAction::make()
6266
->url(CaseResource::getUrl('view', ['record' => $this->getRecord()])),
67+
Action::make('download_sheet')
68+
->label(__('case.view.identity_page.download_sheet'))
69+
->icon(Heroicon::OutlinedArrowDownTray)
70+
->action(fn (): StreamedResponse => app(CaseExportManager::class)->downloadDetailedEvaluationPdf($this->getRecord())),
6371
];
6472
}
6573

app/Filament/Organizations/Resources/Cases/Pages/InterventionPlan/ViewCaseBeneficiaryIntervention.php

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use App\Models\InterventionMeeting;
1414
use App\Models\InterventionService;
1515
use App\Models\Specialist;
16+
use App\Services\CaseExports\CaseExportManager;
1617
use Filament\Actions\Action;
1718
use Filament\Actions\DeleteAction;
1819
use Filament\Forms\Components\RichEditor;
@@ -205,47 +206,10 @@ protected function getViewMeetingObservationsAction(): Action
205206

206207
public function downloadMeetingsTable(): \Symfony\Component\HttpFoundation\StreamedResponse
207208
{
208-
$meetings = $this->beneficiaryIntervention?->meetings()
209-
->with(['specialist.user', 'specialist.roleForDisplay'])
210-
->orderByDesc('id')
211-
->get() ?? collect();
212-
213-
$filename = 'evidenta-sedinte-'.now()->format('Y-m-d').'.csv';
214-
215-
return response()->streamDownload(
216-
function () use ($meetings): void {
217-
$out = fopen('php://output', 'w');
218-
if ($out === false) {
219-
return;
220-
}
221-
fprintf($out, chr(0xEF).chr(0xBB).chr(0xBF));
222-
fputcsv($out, [
223-
__('intervention_plan.labels.meet_number'),
224-
__('intervention_plan.labels.status'),
225-
__('intervention_plan.labels.date'),
226-
__('intervention_plan.labels.time'),
227-
__('intervention_plan.labels.duration'),
228-
__('intervention_plan.labels.specialist'),
229-
]);
230-
$number = $meetings->count();
231-
foreach ($meetings as $meeting) {
232-
fputcsv($out, [
233-
$number,
234-
$meeting->status?->getLabel() ?? '',
235-
$meeting->date?->translatedFormat('d/m/Y') ?? '',
236-
$meeting->time ? \Carbon\Carbon::parse($meeting->time)->format('H:i') : '',
237-
$meeting->duration !== null ? $meeting->duration.' min' : '',
238-
$meeting->specialist?->name_role ?? '',
239-
]);
240-
$number--;
241-
}
242-
fclose($out);
243-
},
244-
$filename,
245-
[
246-
'Content-Type' => 'text/csv; charset=UTF-8',
247-
]
248-
);
209+
abort_unless($this->beneficiaryIntervention instanceof BeneficiaryIntervention, 404);
210+
abort_unless($this->record instanceof Beneficiary, 404);
211+
212+
return app(CaseExportManager::class)->downloadMeetingsCsv($this->beneficiaryIntervention, $this->record);
249213
}
250214

251215
protected function getMeetingNumberForId(int $meetingId): int

app/Filament/Organizations/Resources/Cases/Pages/InterventionPlan/ViewCaseInterventionPlan.php

Lines changed: 3 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use App\Filament\Organizations\Resources\Cases\Pages\InterventionPlan\Widgets\InterventionPlanServicesWidget;
1313
use App\Forms\Components\DatePicker;
1414
use App\Models\Beneficiary;
15+
use App\Services\CaseExports\CaseExportManager;
1516
use Carbon\Carbon;
1617
use Filament\Actions\Action;
1718
use Filament\Actions\EditAction;
@@ -28,6 +29,7 @@
2829
use Illuminate\Contracts\Support\Arrayable;
2930
use Illuminate\Contracts\Support\Htmlable;
3031
use Illuminate\Support\Collection;
32+
use Symfony\Component\HttpFoundation\StreamedResponse;
3133

3234
class ViewCaseInterventionPlan extends ViewRecord
3335
{
@@ -116,85 +118,7 @@ protected function getHeaderActions(): array
116118
->label(__('intervention_plan.actions.download_plan'))
117119
->icon(Heroicon::OutlinedArrowDownTray)
118120
->outlined()
119-
->action(function (): void {
120-
Notification::make()
121-
->info()
122-
->title(__('intervention_plan.actions.download_plan'))
123-
->body(__('intervention_plan.labels.download_plan_coming_soon'))
124-
->send();
125-
}),
126-
Action::make('beneficiary_details')
127-
->record($record)
128-
->slideOver()
129-
->modalHeading(__('case.view.identity_page.fab_beneficiary_details'))
130-
->modalSubmitAction(false)
131-
->modalCancelActionLabel(__('general.action.close'))
132-
->schema([
133-
TextEntry::make('full_name')
134-
->label('Nume și prenume')
135-
->placeholder(''),
136-
TextEntry::make('created_at')
137-
->label('Data creării cazului')
138-
->formatStateUsing(fn (mixed $state): string => self::formatDateState($state))
139-
->placeholder(''),
140-
TextEntry::make('age')
141-
->label(__('field.age'))
142-
->state(fn (Beneficiary $record): string => $record->age !== null ? (string) $record->age : ''),
143-
TextEntry::make('civil_status')
144-
->label(__('field.civil_status'))
145-
->state(fn (Beneficiary $record): string => self::formatEnumLabel($record->civil_status)),
146-
TextEntry::make('children_total_count')
147-
->label('Număr total copii')
148-
->placeholder(''),
149-
TextEntry::make('children_under_18_care_count')
150-
->label('Număr copii în întreținere cu vârsta < 18 ani')
151-
->placeholder(''),
152-
TextEntry::make('legal_residence_city')
153-
->label('Oraș/UAT domiciliu legal')
154-
->state(fn (Beneficiary $record): string => $record->legal_residence?->city?->name ?? ''),
155-
TextEntry::make('effective_residence_city')
156-
->label('Localitate domiciliu efectiv')
157-
->state(fn (Beneficiary $record): string => $record->effective_residence?->city?->name ?? ''),
158-
TextEntry::make('details.studies')
159-
->label(__('field.studies'))
160-
->state(fn (Beneficiary $record): string => self::formatEnumLabel($record->details?->studies)),
161-
TextEntry::make('details.occupation')
162-
->label(__('field.occupation'))
163-
->state(fn (Beneficiary $record): string => self::formatEnumLabel($record->details?->occupation)),
164-
TextEntry::make('details.net_income')
165-
->label('Venit lunar net (din toate sursele)')
166-
->state(function (Beneficiary $record): string {
167-
$income = $record->details?->net_income;
168-
169-
return blank($income) ? '' : "{$income} RON";
170-
}),
171-
TextEntry::make('details.homeownership')
172-
->label('Dreptul de proprietate asupra locuinței primare')
173-
->state(fn (Beneficiary $record): string => self::formatEnumLabel($record->details?->homeownership)),
174-
TextEntry::make('aggressor_relationship')
175-
->label('Relația victimei cu agresorul')
176-
->state(fn (Beneficiary $record): string => self::formatEnumLabel($record->aggressors->first()?->relationship)),
177-
TextEntry::make('aggressor_legal_history')
178-
->label('Aspecte legale agresor')
179-
->state(function (Beneficiary $record): string {
180-
$values = $record->aggressors->first()?->legal_history;
181-
182-
return self::formatCollectionLabels($values);
183-
}),
184-
TextEntry::make('flowPresentation.presentation_mode')
185-
->label('Modalitatea de prezentare')
186-
->state(fn (Beneficiary $record): string => self::formatEnumLabel($record->flowPresentation?->presentation_mode)),
187-
TextEntry::make('flowPresentation.act_location')
188-
->label('Locul producerii actelor de VD')
189-
->state(fn (Beneficiary $record): string => self::formatCollectionLabels($record->flowPresentation?->act_location)),
190-
])
191-
->extraModalFooterActions([
192-
Action::make('view_full_beneficiary')
193-
->label(__('case.view.view_full_beneficiary'))
194-
->url(fn (): string => CaseResource::getUrl('view', ['record' => $record]))
195-
->button()
196-
->close(),
197-
]),
121+
->action(fn (): StreamedResponse => app(CaseExportManager::class)->downloadMonthlyPlanPdf($record)),
198122
];
199123
}
200124

app/Filament/Organizations/Resources/Cases/Pages/ViewCaseIdentity.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
use App\Filament\Organizations\Resources\Cases\CaseResource;
99
use App\Filament\Organizations\Resources\Cases\Schemas\IdentityInfolist;
1010
use App\Models\Beneficiary;
11+
use App\Services\CaseExports\CaseExportManager;
1112
use Filament\Actions\Action;
1213
use Filament\Resources\Pages\ViewRecord;
1314
use Filament\Schemas\Schema;
1415
use Filament\Support\Icons\Heroicon;
1516
use Illuminate\Contracts\Support\Htmlable;
17+
use Symfony\Component\HttpFoundation\StreamedResponse;
1618

1719
class ViewCaseIdentity extends ViewRecord
1820
{
@@ -55,7 +57,7 @@ protected function getHeaderActions(): array
5557
Action::make('download_sheet')
5658
->label(__('case.view.identity_page.download_sheet'))
5759
->icon(Heroicon::OutlinedArrowDownTray)
58-
->url('#'),
60+
->action(fn (): StreamedResponse => app(CaseExportManager::class)->downloadIdentityPdf($record)),
5961
];
6062
}
6163

app/Filament/Organizations/Resources/Cases/Pages/ViewCasePersonalInformation.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88
use App\Filament\Organizations\Resources\Cases\CaseResource;
99
use App\Filament\Organizations\Resources\Cases\Schemas\PersonalInfoInfolist;
1010
use App\Models\Beneficiary;
11+
use App\Services\CaseExports\CaseExportManager;
12+
use Filament\Actions\Action;
1113
use Filament\Resources\Pages\ViewRecord;
1214
use Filament\Schemas\Schema;
15+
use Filament\Support\Icons\Heroicon;
1316
use Illuminate\Contracts\Support\Htmlable;
17+
use Symfony\Component\HttpFoundation\StreamedResponse;
1418

1519
class ViewCasePersonalInformation extends ViewRecord
1620
{
@@ -45,6 +49,10 @@ protected function getHeaderActions(): array
4549
return [
4650
BackAction::make()
4751
->url(CaseResource::getUrl('view', ['record' => $record])),
52+
Action::make('download_sheet')
53+
->label(__('case.view.identity_page.download_sheet'))
54+
->icon(Heroicon::OutlinedArrowDownTray)
55+
->action(fn (): StreamedResponse => app(CaseExportManager::class)->downloadCaseInfoPdf($record)),
4856
];
4957
}
5058

app/Filament/Organizations/Resources/Cases/Resources/CloseFile/Pages/ViewCloseFile.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@
1111
use App\Filament\Organizations\Resources\Cases\Resources\CloseFile\CloseFileResource;
1212
use App\Infolists\Components\DateEntry;
1313
use App\Models\Beneficiary;
14+
use App\Services\CaseExports\CaseExportManager;
15+
use Filament\Actions\Action;
1416
use Filament\Actions\DeleteAction;
1517
use Filament\Infolists\Components\TextEntry;
1618
use Filament\Resources\Pages\ViewRecord;
1719
use Filament\Schemas\Components\Section;
1820
use Filament\Schemas\Components\Tabs;
1921
use Filament\Schemas\Components\Tabs\Tab;
2022
use Filament\Schemas\Schema;
23+
use Filament\Support\Icons\Heroicon;
2124
use Illuminate\Contracts\Support\Htmlable;
25+
use Symfony\Component\HttpFoundation\StreamedResponse;
2226

2327
class ViewCloseFile extends ViewRecord
2428
{
@@ -54,6 +58,11 @@ protected function getHeaderActions(): array
5458
->modalDescription(__('beneficiary.section.close_file.labels.modal_delete_description'))
5559
->successRedirectUrl(CaseResource::getUrl('view', ['record' => $parent]))
5660
->outlined(),
61+
Action::make('download_sheet')
62+
->label(__('case.view.identity_page.download_sheet'))
63+
->icon(Heroicon::OutlinedArrowDownTray)
64+
->outlined()
65+
->action(fn (): StreamedResponse => app(CaseExportManager::class)->downloadCloseFilePdf($this->getRecord())),
5766
];
5867
}
5968

app/Filament/Organizations/Resources/Cases/Resources/InitialEvaluation/Pages/ViewInitialEvaluation.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use App\Infolists\Components\Notice;
1515
use App\Models\Beneficiary;
1616
use App\Models\EvaluateDetails;
17+
use App\Services\CaseExports\CaseExportManager;
18+
use Filament\Actions\Action;
1719
use Filament\Infolists\Components\RepeatableEntry;
1820
use Filament\Infolists\Components\RepeatableEntry\TableColumn;
1921
use Filament\Infolists\Components\TextEntry;
@@ -23,8 +25,10 @@
2325
use Filament\Schemas\Components\Tabs;
2426
use Filament\Schemas\Components\Tabs\Tab;
2527
use Filament\Schemas\Schema;
28+
use Filament\Support\Icons\Heroicon;
2629
use Illuminate\Contracts\Support\Htmlable;
2730
use Illuminate\Support\Str;
31+
use Symfony\Component\HttpFoundation\StreamedResponse;
2832

2933
class ViewInitialEvaluation extends ViewRecord
3034
{
@@ -44,6 +48,10 @@ protected function getHeaderActions(): array
4448
->url($parent instanceof Beneficiary
4549
? CaseResource::getUrl('view', ['record' => $parent])
4650
: CaseResource::getUrl('index')),
51+
Action::make('download_sheet')
52+
->label(__('case.view.identity_page.download_sheet'))
53+
->icon(Heroicon::OutlinedArrowDownTray)
54+
->action(fn (): StreamedResponse => app(CaseExportManager::class)->downloadInitialEvaluationPdf($this->getParentRecord())),
4755
];
4856
}
4957

app/Filament/Organizations/Resources/Cases/Resources/Monitoring/Pages/ViewMonitoring.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
use App\Filament\Organizations\Resources\Cases\Resources\Monitoring\MonitoringResource;
1010
use App\Infolists\Components\SectionHeader;
1111
use App\Models\Beneficiary;
12+
use App\Services\CaseExports\CaseExportManager;
1213
use Carbon\Carbon;
14+
use Filament\Actions\Action;
1315
use Filament\Actions\DeleteAction;
1416
use Filament\Infolists\Components\RepeatableEntry;
1517
use Filament\Infolists\Components\TextEntry;
@@ -19,7 +21,9 @@
1921
use Filament\Schemas\Components\Tabs;
2022
use Filament\Schemas\Components\Tabs\Tab;
2123
use Filament\Schemas\Schema;
24+
use Filament\Support\Icons\Heroicon;
2225
use Illuminate\Contracts\Support\Htmlable;
26+
use Symfony\Component\HttpFoundation\StreamedResponse;
2327

2428
class ViewMonitoring extends ViewRecord
2529
{
@@ -59,6 +63,11 @@ protected function getHeaderActions(): array
5963
->record($this->getRecord())
6064
->successRedirectUrl(CaseResource::getUrl('edit_case_monitoring', ['record' => $parent]))
6165
->outlined(),
66+
Action::make('download_sheet')
67+
->label(__('case.view.identity_page.download_sheet'))
68+
->icon(Heroicon::OutlinedArrowDownTray)
69+
->outlined()
70+
->action(fn (): StreamedResponse => app(CaseExportManager::class)->downloadMonitoringPdf($this->getRecord())),
6271
];
6372
}
6473

0 commit comments

Comments
 (0)