diff --git a/app/Filament/Organizations/Resources/InterventionPlanResource.php b/app/Filament/Organizations/Resources/InterventionPlanResource.php index e9e5ddbf..bf31b75a 100644 --- a/app/Filament/Organizations/Resources/InterventionPlanResource.php +++ b/app/Filament/Organizations/Resources/InterventionPlanResource.php @@ -7,6 +7,10 @@ use App\Filament\Organizations\Resources\InterventionServiceResource\Pages\EditCounselingSheet; use App\Filament\Organizations\Resources\InterventionServiceResource\Pages\EditInterventionService; use App\Filament\Organizations\Resources\InterventionServiceResource\Pages\ViewInterventionService; +use App\Filament\Organizations\Resources\MonthlyPlanResource\Pages\CreateMonthlyPlan; +use App\Filament\Organizations\Resources\MonthlyPlanResource\Pages\EditMonthlyPlanDetails; +use App\Filament\Organizations\Resources\MonthlyPlanResource\Pages\EditMonthlyPlanServicesAndInterventions; +use App\Filament\Organizations\Resources\MonthlyPlanResource\Pages\ViewMonthlyPlan; use App\Models\InterventionPlan; use Filament\Resources\Resource; @@ -24,6 +28,11 @@ public static function getPages(): array 'view_intervention_service' => ViewInterventionService::route('{parent}/service/{record}'), 'edit_intervention_service' => EditInterventionService::route('{parent}/service/{record}/edit'), 'edit_counseling_sheet' => EditCounselingSheet::route('{parent}/service/{record}/editCounselingSheet'), + + 'create_monthly_plan' => CreateMonthlyPlan::route('{parent}/createMonthlyPlan/{copyLastPlan?}'), + 'view_monthly_plan' => ViewMonthlyPlan::route('{parent}/monthlyPlan{record}'), + 'edit_monthly_plan_details' => EditMonthlyPlanDetails::route('{parent}/monthlyPlan{record}/editDetails'), + 'edit_monthly_plan_services_and_interventions' => EditMonthlyPlanServicesAndInterventions::route('{parent}/monthlyPlan{record}/editServicesAndInterventions'), ]; } } diff --git a/app/Filament/Organizations/Resources/InterventionPlanResource/Widgets/Interventions.php b/app/Filament/Organizations/Resources/InterventionPlanResource/Widgets/Interventions.php index 058de199..b719baa8 100644 --- a/app/Filament/Organizations/Resources/InterventionPlanResource/Widgets/Interventions.php +++ b/app/Filament/Organizations/Resources/InterventionPlanResource/Widgets/Interventions.php @@ -12,6 +12,7 @@ class Interventions extends MultiWidget ServicesWidget::class, BenefitsWidget::class, ResultsWidget::class, + MonthlyPlanWidget::class, ]; public function shouldPersistMultiWidgetTabsInSession(): bool diff --git a/app/Filament/Organizations/Resources/InterventionPlanResource/Widgets/MonthlyPlanWidget.php b/app/Filament/Organizations/Resources/InterventionPlanResource/Widgets/MonthlyPlanWidget.php new file mode 100644 index 00000000..64d13b8a --- /dev/null +++ b/app/Filament/Organizations/Resources/InterventionPlanResource/Widgets/MonthlyPlanWidget.php @@ -0,0 +1,92 @@ +query( + fn () => $this->record->monthlyPlans() + ->with(['caseManager', 'beneficiary']) + ->withCount(['monthlyPlanServices', 'monthlyPlanInterventions']) + ) + ->heading(__('intervention_plan.headings.monthly_plans')) + ->headerActions([ + Action::make('create_modal') + ->label(__('intervention_plan.actions.create_monthly_plan')) + ->modalHeading(__('intervention_plan.headings.create_monthly_plan_modal')) + ->modalDescription(__('intervention_plan.labels.create_monthly_plan_modal')) + ->modalSubmitAction( + Action::make('crete_from_last') + ->label(__('intervention_plan.actions.create_monthly_plan_from_last')) + ->url(InterventionPlanResource::getUrl('create_monthly_plan', [ + 'parent' => $this->record, + 'copyLastPlan' => 'copyLastPlan', + ])) + ) + ->modalCancelAction( + Action::make('create_simple') + ->label(__('intervention_plan.actions.create_monthly_plan_simple')) + ->outlined() + ->url(InterventionPlanResource::getUrl('create_monthly_plan', ['parent' => $this->record])) + ) + ->visible(fn () => $this->record->monthlyPlans->count()), + + CreateAction::make() + ->label(__('intervention_plan.actions.create_monthly_plan')) + ->url(InterventionPlanResource::getUrl('create_monthly_plan', ['parent' => $this->record])) + ->visible(fn () => ! $this->record->monthlyPlans->count()), + ]) + ->columns([ + TextColumn::make('interval') + ->label(__('intervention_plan.headings.interval')), + + TextColumn::make('caseManager.full_name') + ->label(__('intervention_plan.headings.case_manager')), + + TextColumn::make('monthly_plan_services_count') + ->label(__('intervention_plan.headings.services_count')), + + TextColumn::make('monthly_plan_interventions_count') + ->label(__('intervention_plan.headings.interventions_count')), + ]) + ->actions([ + ViewAction::make() + ->label(__('general.action.view_details')) + ->url(fn (MonthlyPlan $record) => InterventionPlanResource::getUrl('view_monthly_plan', [ + 'parent' => $this->record, + 'record' => $record, + ])), + ]) + ->recordUrl( + fn (MonthlyPlan $record) => InterventionPlanResource::getUrl('view_monthly_plan', [ + 'parent' => $this->record, + 'record' => $record, + ]) + ) + ->emptyStateHeading(__('intervention_plan.headings.empty_state_monthly_plan_table')) + ->emptyStateDescription(__('intervention_plan.labels.empty_state_monthly_plan_table')) + ->emptyStateIcon('heroicon-o-document'); + } + + public function getDisplayName(): string + { + return __('intervention_plan.headings.monthly_plans'); + } +} diff --git a/app/Filament/Organizations/Resources/MonthlyPlanResource.php b/app/Filament/Organizations/Resources/MonthlyPlanResource.php new file mode 100644 index 00000000..211cfde7 --- /dev/null +++ b/app/Filament/Organizations/Resources/MonthlyPlanResource.php @@ -0,0 +1,27 @@ + Pages\ListMonthlyPlans::route('/'), + 'create' => Pages\CreateMonthlyPlan::route('/create'), + 'edit' => Pages\EditMonthlyPlanDetails::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/CreateMonthlyPlan.php b/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/CreateMonthlyPlan.php new file mode 100644 index 00000000..a751d5c2 --- /dev/null +++ b/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/CreateMonthlyPlan.php @@ -0,0 +1,119 @@ +parent) + ->getCreateMonthlyPlan($this->getRecord()); + } + + public function getTitle(): string|Htmlable + { + return __('intervention_plan.headings.create_monthly_plan'); + } + + protected function getRedirectUrl(): string + { + return InterventionPlanResource::getUrl('view_monthly_plan', [ + 'parent' => $this->parent, + 'record' => $this->getRecord(), + ]); + } + + protected function afterFill(): void + { + $copyLastPlan = (bool) request('copyLastPlan'); + + if (! $copyLastPlan) { + $this->services = [ + [ + 'start_date' => now(), + 'end_date' => now()->addMonth(), + 'institution' => Filament::getTenant()->name, + ], + ]; + + $this->form->fill([ + 'start_date' => now(), + 'end_date' => now()->addMonth(), + 'case_manager_user_id' => $this->parent + ->beneficiary + ->managerTeam + ->first() + ?->user_id, + 'specialists' => $this->parent + ->beneficiary + ->specialistsTeam + ->pluck('id'), + 'intervention_plan_id' => $this->parent->id, + ]); + + return; + } + + $lastPlan = $this->parent + ->monthlyPlans() + ->with(['monthlyPlanServices.monthlyPlanInterventions']) + ->orderByDesc('id') + ->first(); + $this->services = $lastPlan?->monthlyPlanServices + ->toArray(); + + $this->form->fill($lastPlan + ?->toArray()); + } + + public function getSteps(): array + { + return [ + Step::make('details') + ->label(__('intervention_plan.headings.monthly_plan_details')) + ->schema(EditMonthlyPlanDetails::getSchema($this->parent->beneficiary)), + + Step::make('services_and_interventions') + ->label(__('intervention_plan.headings.services_and_interventions')) + ->afterStateHydrated(function (Set $set, Get $get) { + if (! $this->services) { + return; + } + + $set('monthlyPlanServices', $this->services); + foreach ($get('monthlyPlanServices') as $key => $service) { + $interventionPath = \sprintf('monthlyPlanServices.%s.monthlyPlanInterventions', $key); + $interventions = $this->services[$key]['monthly_plan_interventions'] ?? [[]]; + + $set($interventionPath, $interventions); + } + }) + ->schema(EditMonthlyPlanServicesAndInterventions::getSchema()), + ]; + } +} diff --git a/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/EditMonthlyPlanDetails.php b/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/EditMonthlyPlanDetails.php new file mode 100644 index 00000000..8e2196d5 --- /dev/null +++ b/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/EditMonthlyPlanDetails.php @@ -0,0 +1,98 @@ +parent) + ->getViewMonthlyPlan($this->getRecord()); + } + + public function getTitle(): string + { + return __('intervention_plan.headings.edit_monthly_plan_title'); + } + + protected function getRedirectUrl(): ?string + { + return InterventionPlanResource::getUrl('view_monthly_plan', [ + 'parent' => $this->parent, + 'record' => $this->getRecord(), + 'tab' => \sprintf('-%s-tab', $this->getTabSlug()), + ]); + } + + protected function getTabSlug(): string + { + return Str::slug(__('intervention_plan.headings.monthly_plan_details')); + } + + public function form(Form $form): Form + { + return $form->schema(self::getSchema($this->parent->beneficiary)); + } + + public static function getSchema(?Beneficiary $beneficiary = null): array + { + return [ + Section::make() + ->maxWidth('3xl') + ->columns() + ->schema([ + DatePicker::make('start_date') + ->label(__('intervention_plan.labels.monthly_plan_start_date')) + ->displayFormat('d-m-Y') + ->required(), + + DatePicker::make('end_date') + ->label(__('intervention_plan.labels.monthly_plan_end_date')) + ->displayFormat('d-m-Y') + ->required(), + + Select::make('case_manager_user_id') + ->label(__('intervention_plan.labels.case_manager')) + ->placeholder(__('intervention_plan.placeholders.specialist')) + ->options( + fn () => $beneficiary + ->specialistsMembers + ->pluck('full_name', 'id') + ) + ->required(), + + Select::make('specialists') + ->label(__('intervention_plan.labels.specialists')) + ->placeholder(__('intervention_plan.placeholders.specialists')) + ->multiple() + ->options( + fn () => $beneficiary + ->specialistsTeam + ->pluck('name_role', 'id') + ) + ->required(), + ]), + + Hidden::make('intervention_plan_id'), + ]; + } +} diff --git a/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/EditMonthlyPlanServicesAndInterventions.php b/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/EditMonthlyPlanServicesAndInterventions.php new file mode 100644 index 00000000..86008bbd --- /dev/null +++ b/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/EditMonthlyPlanServicesAndInterventions.php @@ -0,0 +1,192 @@ +parent) + ->getViewMonthlyPlan($this->getRecord()); + } + + public function getTitle(): string + { + return __('intervention_plan.headings.edit_monthly_plan_services_and_interventions_title'); + } + + protected function getRedirectUrl(): ?string + { + return InterventionPlanResource::getUrl('view_monthly_plan', [ + 'parent' => $this->parent, + 'record' => $this->getRecord(), + 'tab' => \sprintf('-%s-tab', $this->getTabSlug()), + ]); + } + + protected function getTabSlug(): string + { + return Str::slug(__('intervention_plan.headings.services_and_interventions')); + } + + public function form(Form $form): Form + { + return $form->schema([ + Section::make(__('intervention_plan.headings.services_and_interventions')) + ->schema($this->getSchema()), + ]); + } + + public static function getSchema(): array + { + return [ + Repeater::make('monthlyPlanServices') + ->relationship('monthlyPlanServices') + ->hiddenLabel() + ->itemLabel(function () { + static $index = 0; + + return __('intervention_plan.headings.service_count', [ + 'number' => ++$index, + ]); + }) + ->addAction( + fn (Action $action) => $action + ->link() + ->label(__('intervention_plan.actions.add_service_repeater')) + ->color('primary') + ) + ->addActionAlignment(Alignment::Left) + ->schema([ + Grid::make() + ->maxWidth('3xl') + ->schema([ + Select::make('service_id') + ->label(__('intervention_plan.labels.service_type')) + ->placeholder(__('intervention_plan.placeholders.select_service')) + ->options(function (?int $state) { + $services = Service::query() + ->active() + ->get() + ->pluck('name', 'id'); + + if ($state && ! isset($services[$state])) { + $services[$state] = Service::find($state)->name; + } + + return $services; + }) + ->required() + ->live(), + + TextInput::make('institution') + ->label(__('intervention_plan.labels.responsible_institution')) + ->placeholder(__('intervention_plan.placeholders.responsible_institution')) + ->default(Filament::getTenant()->name) + ->maxLength(200), + + TextInput::make('responsible_person') + ->label(__('intervention_plan.labels.responsible_person')) + ->placeholder(__('intervention_plan.placeholders.responsible_person')) + ->columnSpanFull() + ->maxLength(200), + + DatePicker::make('start_date') + ->displayFormat('d-m-Y') + ->label(__('intervention_plan.labels.monthly_plan_service_interval_start')), + + DatePicker::make('end_date') + ->displayFormat('d-m-Y') + ->label(__('intervention_plan.labels.monthly_plan_service_interval_end')), + + Textarea::make('objective') + ->label(__('intervention_plan.labels.service_objective')) + ->placeholder(__('intervention_plan.placeholders.service_objective')) + ->columnSpanFull() + ->maxLength(2000), + ]), + + TableRepeater::make('monthlyPlanInterventions') + ->relationship('monthlyPlanInterventions') + ->hiddenLabel() + ->hideLabels() + ->addActionLabel(__('intervention_plan.actions.add_intervention_repeater')) + ->schema([ + Placeholder::make('number') + ->label(__('intervention_plan.labels.count')) + ->hiddenLabel() + ->content(function () { + static $index = 0; + + return ++$index; + }), + + Select::make('service_intervention_id') + ->label(__('intervention_plan.headings.interventions')) + ->placeholder(__('intervention_plan.placeholders.select_intervention')) + ->options( + function (Get $get, ?int $state) { + if (! $get('../../service_id')) { + return []; + } + + $interventions = Service::find((int) $get('../../service_id')) + ->load('activeServiceInterventions') + ->activeServiceInterventions + ->pluck('name', 'id'); + + if ($state && ! isset($interventions[$state])) { + $interventions[$state] = ServiceIntervention::find($state)->name; + } + + return $interventions; + } + ), + + TextInput::make('objections') + ->label(__('intervention_plan.labels.objections')) + ->placeholder(__('intervention_plan.placeholders.monthly_plan_objective')) + ->maxLength(200), + + TextInput::make('observations') + ->label(__('intervention_plan.labels.observations')) + ->placeholder(__('intervention_plan.placeholders.monthly_plan_observations')) + ->maxLength(200), + ]), + + Textarea::make('service_details') + ->label(__('intervention_plan.labels.service_details')) + ->placeholder(__('intervention_plan.placeholders.service_details')), + ]), + ]; + } +} diff --git a/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/ListMonthlyPlans.php b/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/ListMonthlyPlans.php new file mode 100644 index 00000000..39b7b4cb --- /dev/null +++ b/app/Filament/Organizations/Resources/MonthlyPlanResource/Pages/ListMonthlyPlans.php @@ -0,0 +1,21 @@ +parent) + ->getViewMonthlyPlan($this->getRecord()); + } + + public function getTitle(): string|Htmlable + { + return __('intervention_plan.headings.monthly_plan'); + } + + public function getHeaderActions(): array + { + return [ + DeleteAction::make() + ->label(__('intervention_plan.actions.delete_monthly_plan')) + ->icon('heroicon-o-trash') + ->modalHeading(__('intervention_plan.headings.delete_monthly_plan_modal')) + ->modalSubmitActionLabel(__('intervention_plan.actions.delete_monthly_plan')) + ->successRedirectUrl( + BeneficiaryResource::getUrl('view_intervention_plan', [ + 'parent' => $this->parent->beneficiary, + 'record' => $this->parent, + ]) + ) + ->outlined(), + ]; + } + + public function infolist(Infolist $infolist): Infolist + { + $this->getRecord() + ->load([ + 'monthlyPlanServices.service', + 'monthlyPlanServices.monthlyPlanInterventions.serviceIntervention', + ]); + + return $infolist->schema([ + Tabs::make() + ->persistTabInQueryString() + ->schema([ + Tab::make(__('intervention_plan.headings.monthly_plan_details')) + ->schema([ + Section::make() + ->maxWidth('3xl') + ->columns() + ->schema([ + SectionHeader::make('monthly_plan_details') + ->state(__('intervention_plan.headings.monthly_plan_details')) + ->action( + Action::make('edit_details') + ->label(__('general.action.edit')) + ->link() + ->url( + InterventionPlanResource::getUrl('edit_monthly_plan_details', [ + 'parent' => $this->parent, + 'record' => $this->getRecord(), + ]) + ) + ), + + TextEntry::make('beneficiary.full_name') + ->label(__('intervention_plan.labels.full_name')), + + TextEntry::make('beneficiary.organization_beneficiary_id') + ->label(__('intervention_plan.labels.organization_beneficiary_id')), + + TextEntry::make('start_date') + ->label(__('intervention_plan.labels.plan_date')) + ->date('d.m.Y'), + + TextEntry::make('interval') + ->label(__('intervention_plan.labels.interval')), + + TextEntry::make('caseManager.full_name') + ->label(__('intervention_plan.labels.case_manager')), + + TextEntry::make('specialists') + ->label(__('intervention_plan.labels.specialists')) + ->formatStateUsing( + fn (string $state) => collect(explode(',', $state)) + ->map( + fn (string $specialistID) => Specialist::find((int) (trim($specialistID))) + ->name_role + ) + ->implode(', ') + ) + ->listWithLineBreaks(), + ]), + + ]), + + Tab::make(__('intervention_plan.headings.services_and_interventions')) + ->schema([ + Section::make() + ->maxWidth('3xl') + ->schema([ + SectionHeader::make('monthly_plan_details') + ->state(__('intervention_plan.headings.services_and_interventions')) + ->action( + Action::make('edit_details') + ->label(__('general.action.edit')) + ->link() + ->url( + InterventionPlanResource::getUrl('edit_monthly_plan_services_and_interventions', [ + 'parent' => $this->parent, + 'record' => $this->getRecord(), + ]) + ) + ), + + RepeatableEntry::make('monthlyPlanServices') + ->hiddenLabel() + ->columns() + ->columnSpanFull() + ->schema([ + SectionHeader::make('service.name'), + + TextEntry::make('institution') + ->label(__('intervention_plan.labels.responsible_institution')), + + TextEntry::make('responsible_person') + ->label(__('intervention_plan.labels.responsible_person')), + + TextEntry::make('start_date') + ->label(__('intervention_plan.labels.monthly_plan_service_interval_start')) + ->formatStateUsing( + fn ($state) => $state === '-' ? $state : Carbon::parse($state)->format('d.m.Y') + ), + + TextEntry::make('end_date') + ->label(__('intervention_plan.labels.monthly_plan_service_interval_end')) + ->formatStateUsing( + fn ($state) => $state === '-' ? $state : Carbon::parse($state)->format('d.m.Y') + ), + + TextEntry::make('objective') + ->label(__('intervention_plan.labels.service_objective')), + + TableEntry::make('monthlyPlanInterventions') + ->columnSpanFull() + ->hiddenLabel() + ->schema([ + TextEntry::make('serviceIntervention.name') + ->label(__('intervention_plan.headings.interventions')) + ->hiddenLabel(), + + TextEntry::make('objections') + ->label(__('intervention_plan.labels.objections')) + ->hiddenLabel(), + + TextEntry::make('observations') + ->label(__('intervention_plan.labels.observations')) + ->hiddenLabel(), + ]), + + TextEntry::make('service_details') + ->label(__('intervention_plan.labels.service_details')) + ->columnSpanFull(), + ]), + ]), + + ]), + ]), + + ]); + } +} diff --git a/app/Infolists/Components/TableEntry.php b/app/Infolists/Components/TableEntry.php new file mode 100644 index 00000000..c69e8486 --- /dev/null +++ b/app/Infolists/Components/TableEntry.php @@ -0,0 +1,123 @@ +breakPoint = $breakPoint; + + return $this; + } + + public function columnWidths(array | Closure $widths = []): static + { + $this->columnWidths = $widths; + + return $this; + } + + public function withoutHeader(bool|Closure $condition = true): static + { + $this->withoutHeader = $condition; + + return $this; + } + + public function emptyLabel(bool|string|Closure|null $label = null): static + { + $this->emptyLabel = $label; + + return $this; + } + + public function alignHeaders(string|Closure $alignment = 'left'): static + { + $this->headersAlignment = $alignment; + + return $this; + } + + public function shouldHideHeader(): bool + { + return $this->evaluate($this->withoutHeader); + } + + public function getBreakPoint(): string + { + return $this->breakPoint; + } + + public function getColumnWidths(): array + { + return $this->evaluate($this->columnWidths); + } + + public function getEmptyLabel(): bool|string|null + { + return $this->evaluate($this->emptyLabel); + } + + public function getHeadersAlignment(): string + { + return $this->evaluate($this->headersAlignment) ?? 'left'; + } + + public function getHeaders(): array + { + $mergedHeaders = []; + + $customHeaders = $this->evaluate($this->headers); + + foreach ($this->getChildComponents() as $field) { + if ($field instanceof Hidden || $field->isHidden()) { + continue; + } + + $key = method_exists($field, 'getName') ? $field->getName() : $field->getId(); + + $isRequired = false; + + if (property_exists($field, 'isRequired') && \is_bool($field->isRequired())) { + $isRequired = $field->isRequired(); + + if (property_exists($field, 'isMarkedAsRequired') && \is_bool($field->isMarkedAsRequired)) { + $isRequired = $field->isRequired() && $field->isMarkedAsRequired; + } + } + + $item = [ + 'label' => $customHeaders[$key] ?? $field->getLabel(), + 'width' => $this->getColumnWidths()[$key] ?? null, + 'required' => $isRequired, + ]; + + $mergedHeaders[method_exists($field, 'getName') ? $field->getName() : $field->getId()] = $item; + } + + $this->headers = $mergedHeaders; + + return $this->evaluate($this->headers); + } +} diff --git a/app/Models/InterventionPlan.php b/app/Models/InterventionPlan.php index c1fc78ab..769fb348 100644 --- a/app/Models/InterventionPlan.php +++ b/app/Models/InterventionPlan.php @@ -42,4 +42,9 @@ public function beneficiaryInterventions(): HasManyThrough { return $this->hasManyThrough(BeneficiaryIntervention::class, InterventionService::class); } + + public function monthlyPlans(): HasMany + { + return $this->hasMany(MonthlyPlan::class); + } } diff --git a/app/Models/MonthlyPlan.php b/app/Models/MonthlyPlan.php new file mode 100644 index 00000000..70892b98 --- /dev/null +++ b/app/Models/MonthlyPlan.php @@ -0,0 +1,68 @@ + 'date', + 'end_date' => 'date', + 'specialists' => 'collection', + ]; + + public function organization(): BelongsToThrough + { + return $this->belongsToThrough(Organization::class, InterventionPlan::class); + } + + public function interventionPlan(): BelongsTo + { + return $this->belongsTo(InterventionPlan::class); + } + + public function beneficiary(): BelongsToThrough + { + return $this->belongsToThrough(Beneficiary::class, InterventionPlan::class); + } + + public function monthlyPlanServices(): HasMany + { + return $this->hasMany(MonthlyPlanService::class); + } + + public function monthlyPlanInterventions(): HasManyThrough + { + return $this->hasManyThrough(MonthlyPlanInterventions::class, MonthlyPlanService::class); + } + + public function caseManager(): BelongsTo + { + return $this->belongsTo(User::class, 'case_manager_user_id'); + } + + public function getIntervalAttribute(): string + { + return \sprintf('%s - %s', $this->start_date->format('d.m.Y'), $this->end_date->format('d.m.Y')); + } +} diff --git a/app/Models/MonthlyPlanInterventions.php b/app/Models/MonthlyPlanInterventions.php new file mode 100644 index 00000000..1c94bd69 --- /dev/null +++ b/app/Models/MonthlyPlanInterventions.php @@ -0,0 +1,36 @@ +belongsTo(MonthlyPlan::class); + } + + public function monthlyPlanService(): BelongsTo + { + return $this->belongsTo(MonthlyPlanService::class); + } + + public function serviceIntervention(): BelongsTo + { + return $this->belongsTo(ServiceIntervention::class); + } +} diff --git a/app/Models/MonthlyPlanService.php b/app/Models/MonthlyPlanService.php new file mode 100644 index 00000000..a602cdaa --- /dev/null +++ b/app/Models/MonthlyPlanService.php @@ -0,0 +1,41 @@ +belongsTo(MonthlyPlan::class); + } + + public function service(): BelongsTo + { + return $this->belongsTo(Service::class); + } + + public function monthlyPlanInterventions(): HasMany + { + return $this->hasMany(MonthlyPlanInterventions::class); + } +} diff --git a/app/Models/Organization.php b/app/Models/Organization.php index de9d8307..5aa3003d 100644 --- a/app/Models/Organization.php +++ b/app/Models/Organization.php @@ -16,6 +16,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\MorphToMany; use Spatie\Image\Enums\Fit; @@ -81,11 +82,16 @@ public function communityProfile(): HasOne }); } - public function monitorings() + public function monitorings(): HasManyThrough { return $this->hasManyThrough(Monitoring::class, Beneficiary::class); } + public function monthlyPlans(): HasManyThrough + { + return $this->hasManyThrough(MonthlyPlan::class, InterventionPlan::class); + } + public function registerMediaCollections(): void { $this->addMediaCollection('logo') diff --git a/app/Models/Service.php b/app/Models/Service.php index 312f7f2b..f7763688 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -10,8 +10,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Relations\HasManyThrough; -use Illuminate\Database\Eloquent\Relations\HasOne; class Service extends Model { @@ -27,16 +25,16 @@ class Service extends Model 'counseling_sheet' => CounselingSheet::class, ]; -// public function counselingSheet(): HasOne -// { -// -// } - public function serviceInterventions(): HasMany { return $this->hasMany(ServiceIntervention::class); } + public function activeServiceInterventions(): HasMany + { + return $this->serviceInterventions()->active(); + } + public function interventions(): HasMany { return $this->hasMany(Intervention::class); diff --git a/app/Policies/MonthlyPlanPolicy.php b/app/Policies/MonthlyPlanPolicy.php new file mode 100644 index 00000000..b5b27490 --- /dev/null +++ b/app/Policies/MonthlyPlanPolicy.php @@ -0,0 +1,67 @@ +hasAccessToBeneficiary($monthlyPlan->beneficiary); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return true; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, MonthlyPlan $monthlyPlan): bool + { + return $user->hasAccessToBeneficiary($monthlyPlan->beneficiary); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, MonthlyPlan $monthlyPlan): bool + { + return $user->hasAccessToBeneficiary($monthlyPlan->beneficiary); + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, MonthlyPlan $monthlyPlan): bool + { + return true; + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, MonthlyPlan $monthlyPlan): bool + { + return true; + } +} diff --git a/app/Services/Breadcrumb/InterventionPlanBreadcrumb.php b/app/Services/Breadcrumb/InterventionPlanBreadcrumb.php index a67241e0..f1c128ce 100644 --- a/app/Services/Breadcrumb/InterventionPlanBreadcrumb.php +++ b/app/Services/Breadcrumb/InterventionPlanBreadcrumb.php @@ -10,6 +10,7 @@ use App\Models\BeneficiaryIntervention; use App\Models\InterventionPlan; use App\Models\InterventionService; +use App\Models\MonthlyPlan; class InterventionPlanBreadcrumb { @@ -62,4 +63,25 @@ public function getInterventionBreadcrumb(BeneficiaryIntervention $record): arra ]) => $record->organizationServiceIntervention->serviceInterventionWithoutStatusCondition->name] ); } + + public function getViewMonthlyPlan(MonthlyPlan $record): array + { + return array_merge( + $this->getInterventionPlanBreadcrumb(), + [self::$resourcePath::getUrl('view_monthly_plan', [ + 'parent' => $this->record, + 'record' => $record, + ]) => __('intervention_plan.headings.monthly_plan')] + ); + } + + public function getCreateMonthlyPlan(): array + { + return array_merge( + $this->getInterventionPlanBreadcrumb(), + [self::$resourcePath::getUrl('create_monthly_plan', [ + 'parent' => $this->record, + ]) => __('intervention_plan.headings.create_monthly_plan')] + ); + } } diff --git a/database/migrations/2024_12_16_204001_create_monthly_plans_table.php b/database/migrations/2024_12_16_204001_create_monthly_plans_table.php new file mode 100644 index 00000000..71c3dadd --- /dev/null +++ b/database/migrations/2024_12_16_204001_create_monthly_plans_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignIdFor(InterventionPlan::class); + $table->date('start_date'); + $table->date('end_date'); + $table->foreignIdFor(User::class, 'case_manager_user_id'); + $table->json('specialists'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('monthly_plans'); + } +}; diff --git a/database/migrations/2024_12_16_210909_create_monthly_plan_services_table.php b/database/migrations/2024_12_16_210909_create_monthly_plan_services_table.php new file mode 100644 index 00000000..f0b697df --- /dev/null +++ b/database/migrations/2024_12_16_210909_create_monthly_plan_services_table.php @@ -0,0 +1,39 @@ +id(); + $table->foreignIdFor(MonthlyPlan::class); + $table->foreignIdFor(Service::class)->nullable()->constrained()->cascadeOnDelete(); + $table->string('institution')->nullable(); + $table->string('responsible_person')->nullable(); + $table->date('start_date')->nullable(); + $table->date('end_date')->nullable(); + $table->text('objective')->nullable(); + $table->text('service_details')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('monthly_plan_services'); + } +}; diff --git a/database/migrations/2024_12_16_213316_create_monthly_plan_interventions_table.php b/database/migrations/2024_12_16_213316_create_monthly_plan_interventions_table.php new file mode 100644 index 00000000..d7e3391d --- /dev/null +++ b/database/migrations/2024_12_16_213316_create_monthly_plan_interventions_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignIdFor(MonthlyPlanService::class)->constrained()->cascadeOnDelete(); + $table->foreignIdFor(ServiceIntervention::class)->nullable()->constrained()->cascadeOnDelete(); + $table->string('objections')->nullable(); + $table->string('observations')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('monthly_plan_interventions'); + } +}; diff --git a/lang/ro/intervention_plan.php b/lang/ro/intervention_plan.php index f5a7ef78..434a8f88 100644 --- a/lang/ro/intervention_plan.php +++ b/lang/ro/intervention_plan.php @@ -98,6 +98,7 @@ 'next_meeting' => 'Următoarea', 'empty_state_table_without_intervetntions' => 'Adaugă intervenții în planul beneficiarului, pentru a putea documenta activitățile și ședințele realizate în cadrul serviciilor', 'empty_state_result_table' => 'Poți adăuga rezultate acum sau oricând după crearea planului', + 'empty_state_monthly_plan_table' => 'Poți adăuga planuri lunare acum sau oricând după crearea planului', 'result' => 'Rezultat observat', 'started_at' => 'Demers inițiat la', 'ended_at' => 'Realizat/ soluționat la', @@ -158,9 +159,20 @@ 'integration_and_participation_in_social_service_observations' => 'Alte observații legate de integrare și participare în serviciul social', 'count' => 'Nr', 'social_relationship' => 'Relația socială #:number', + 'monthly_plan_start_date' => 'Început perioadă de referință', + 'monthly_plan_end_date' => 'Final perioadă de referință', + 'case_manager' => 'Manager caz', + 'specialists' => 'Echipă caz', + 'responsible_person' => 'Persoane responsabile', + 'service_objective' => 'Obiective specifice serviciu', + 'monthly_plan_service_interval_start' => 'Început perioadă de acordare', + 'monthly_plan_service_interval_end' => 'Final perioadă de acordare', + 'organization_beneficiary_id' => 'Nr Caz', + 'create_monthly_plan_modal' => 'Poți alege să începi cu plan lunar necompletată sau cu un duplicat al ultimului plan lunar realizat pe acest caz. Dacă vei porni de la un duplicat, va trebui să modifici informațiile care s-au schimbat de la o lună la alta.', 'beneficiary' => 'Beneficiar', 'actions' => 'Acțiuni', 'selected_interval' => 'Interval selectat', + 'service_details' => 'Detalii acordare serviciu', ], 'headings' => [ @@ -208,6 +220,7 @@ 'edit_counseling_sheet' => 'Editează fișă consiliere', 'results_table' => 'Rezultate realizate', 'empty_state_result_table' => 'Nici un rezultat adăugat', + 'empty_state_monthly_plan_table' => 'Nici un plan lunar adăugat', 'result' => 'Rezultat', 'specialist' => 'Specialist', 'started_at' => 'Inițiat la', @@ -220,8 +233,22 @@ 'professional_experience' => 'Experiența profesională', 'children_details' => 'Date adiționale despre copii', 'integration_and_participation_in_social_service' => 'Integrare și participare în serviciul social', + 'monthly_plans' => 'Planuri lunare', + 'monthly_plan_details' => 'Detalii plan', + 'services_and_interventions' => 'Servicii și intervenții', + 'create_monthly_plan' => 'Creează plan lunar', + 'service_count' => 'Serviciul :number', + 'interval' => 'Perioada de referință', + 'case_manager' => 'Manager caz', + 'services_count' => 'Nr servicii', + 'interventions_count' => 'Nr intervenții', + 'monthly_plan' => 'Plan de intervenție lunar', + 'create_monthly_plan_modal' => 'Crează un plan lunar de intervenție nou', 'dashboard_intervention_table' => 'Sumar intervenții', 'dashboard_intervention_table_empty_state' => 'Odată deschis primul caz și creat primul plan de intervenție pentru acesta, o listă a intervențiilor va fi afișată în acest tabel.', + 'edit_monthly_plan_title' => 'Editează plan lunar', + 'edit_monthly_plan_services_and_interventions_title' => 'Editează servicii si intervenții', + 'delete_monthly_plan_modal' => 'Șterge plan lunar', ], 'actions' => [ @@ -229,7 +256,9 @@ 'edit' => 'Editează', 'edit_intervention_plan' => 'Actualizează detalii', 'add_service' => 'Adaugă serviciu', + 'add_service_repeater' => 'Adaugă încă un serviciu', 'add_intervention' => 'Adaugă intervenție', + 'add_intervention_repeater' => 'Adaugă intervenție', 'delete_service' => 'Șterge serviciu', 'add_meeting' => 'Adaugă o ședință sau o activitate', 'delete_meeting' => 'Șterge ședința', @@ -244,6 +273,10 @@ 'delete_result' => 'Șterge rezultat', 'add_social_relationship' => 'Adaugă încă o relație socială', 'view_children_identity' => 'Vezi detalii identitate copii', + 'create_monthly_plan' => 'Crează un plan', + 'create_monthly_plan_from_last' => 'Pornește de la un duplicat', + 'create_monthly_plan_simple' => 'Începe cu un plan necompletată', + 'delete_monthly_plan' => 'Șterge planul lunar', ], 'placeholders' => [ @@ -251,6 +284,7 @@ 'organization_service' => 'Alege un serviciu', 'institution' => 'Introdu numele organizației', 'specialist' => 'Alege un specialist', + 'specialists' => 'Alege specialiști', 'objections' => 'Descrieți pe scurt obiectivele acordării serviciului', 'benefit_category' => 'Alege o categorie', 'result_observations' => 'Notați pe scurt orice observații sau notiție relevante pentru activitate', @@ -267,6 +301,14 @@ 'peoples' => 'Introdu un număr', 'add_details' => 'Adaugă detalii', 'select_age' => 'Alege un an', + 'select_service' => 'Alege un serviciu', + 'responsible_institution' => 'Introdu o instituție', + 'responsible_person' => 'Introdu un responsabil', + 'service_objective' => 'Descrie obiectivele specifice ale acordării serviciului în perioada de acordare', + 'select_intervention' => 'Alege o intervenție', + 'monthly_plan_objective' => 'Specifică obiective', + 'monthly_plan_observations' => 'Specifică observații', 'search_by_beneficiary_or_specialist' => 'Caută caz sau specialist', + 'service_details' => 'Adaugă orice detalii relevante acordării intervenției sau intervențiilor', ], ]; diff --git a/resources/views/infolists/components/table-entry.blade.php b/resources/views/infolists/components/table-entry.blade.php new file mode 100644 index 00000000..20e921e5 --- /dev/null +++ b/resources/views/infolists/components/table-entry.blade.php @@ -0,0 +1,118 @@ +@php + use Filament\Forms\Components\Actions\Action; + + $containers = $getChildComponentContainers(); + + $headers = $getHeaders(); + $columnWidths = $getColumnWidths(); + $breakPoint = $getBreakPoint(); + $hasContainers = count($containers) > 0; + $hasHiddenHeader = $shouldHideHeader(); + $statePath = $getStatePath(); + + $emptyLabel = $getEmptyLabel(); + +@endphp + + +
merge($getExtraAttributes())->class([ + 'filament-table-repeater-component space-y-6 relative', + match ($breakPoint) { + 'sm' => 'break-point-sm', + 'lg' => 'break-point-lg', + 'xl' => 'break-point-xl', + '2xl' => 'break-point-2xl', + default => 'break-point-md', + } + ]) }} + > + @if (count($containers) || $emptyLabel !== false) +
! $hasContainers && $breakPoint === 'sm', + 'md:ring-gray-950/5 dark:md:ring-white/20' => ! $hasContainers && $breakPoint === 'md', + 'lg:ring-gray-950/5 dark:lg:ring-white/20' => ! $hasContainers && $breakPoint === 'lg', + 'xl:ring-gray-950/5 dark:xl:ring-white/20' => ! $hasContainers && $breakPoint === 'xl', + '2xl:ring-gray-950/5 dark:2xl:ring-white/20' => ! $hasContainers && $breakPoint === '2xl', + ])> + + $hasHiddenHeader, + 'filament-table-repeater-header rounded-t-xl overflow-hidden border-b border-gray-950/5 dark:border-white/20' => ! $hasHiddenHeader, + ])> + + @foreach ($headers as $key => $header) + + @endforeach + + + + @if (count($containers)) + @foreach ($containers as $uuid => $row) + + @foreach($row->getComponents() as $cell) + @if(! $cell instanceof \Filament\Forms\Components\Hidden && ! $cell->isHidden()) + + @else + {{ $cell }} + @endif + @endforeach + + + @endforeach + @else + + + + @endif + +
$loop->first, + 'ltr:rounded-tr-xl rtl:rounded-tl-xl' => $loop->last, + match($getHeadersAlignment()) { + 'center' => 'text-center', + 'right' => 'text-right rtl:text-left', + default => 'text-left rtl:text-right' + } + ]) + @if ($header['width']) + style="width: {{ $header['width'] }}" + @endif + > + {{ $header['label'] }} + @if ($header['required']) + + * + + @endif +
$cell->isLabelHidden(), + ]) + @php + $cellKey = method_exists($cell, 'getName') ? $cell->getName() : $cell->getId(); + @endphp + @if ( + $columnWidths && + isset($columnWidths[$cellKey]) + ) + style="width: {{ $columnWidths[$cellKey] }}" + @endif + > + {{ $cell }} +
+ {{ $emptyLabel ?: __('filament-table-repeater::components.repeater.empty.label') }} +
+
+ @endif +
+