Compare commits
3 Commits
8ec4e4eb59
...
ff40b9e805
Author | SHA1 | Date |
---|---|---|
philipp lang | ff40b9e805 | |
philipp lang | 92997aa78f | |
philipp lang | ee02b8df3a |
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Actions\DbMaintainAction;
|
use App\Actions\DbMaintainAction;
|
||||||
|
use App\Form\Actions\PreventionRememberAction;
|
||||||
use App\Initialize\InitializeMembers;
|
use App\Initialize\InitializeMembers;
|
||||||
use App\Invoice\Actions\InvoiceSendAction;
|
use App\Invoice\Actions\InvoiceSendAction;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
|
@ -19,6 +20,7 @@ class Kernel extends ConsoleKernel
|
||||||
InvoiceSendAction::class,
|
InvoiceSendAction::class,
|
||||||
InitializeMembers::class,
|
InitializeMembers::class,
|
||||||
DbMaintainAction::class,
|
DbMaintainAction::class,
|
||||||
|
PreventionRememberAction::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,6 +32,7 @@ class Kernel extends ConsoleKernel
|
||||||
{
|
{
|
||||||
$schedule->command(DbMaintainAction::class)->daily();
|
$schedule->command(DbMaintainAction::class)->daily();
|
||||||
$schedule->command(InitializeMembers::class)->dailyAt('03:00');
|
$schedule->command(InitializeMembers::class)->dailyAt('03:00');
|
||||||
|
$schedule->command(PreventionRememberAction::class)->dailyAt('11:00');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -35,6 +35,7 @@ class FormStoreAction
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
'is_private' => 'boolean',
|
'is_private' => 'boolean',
|
||||||
'export' => 'nullable|array',
|
'export' => 'nullable|array',
|
||||||
|
'needs_prevention' => 'present|boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ class FormUpdateAction
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
'is_private' => 'boolean',
|
'is_private' => 'boolean',
|
||||||
'export' => 'nullable|array',
|
'export' => 'nullable|array',
|
||||||
|
'needs_prevention' => 'present|boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form\Actions;
|
||||||
|
|
||||||
|
use App\Form\Models\Participant;
|
||||||
|
use App\Prevention\Mails\PreventionRememberMail;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class PreventionRememberAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public string $commandSignature = 'prevention:remember';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$query = Participant::whereHas('form', fn ($form) => $form->where('needs_prevention', true))
|
||||||
|
->where(
|
||||||
|
fn ($q) => $q
|
||||||
|
->where('last_remembered_at', '<=', now()->subWeeks(2))
|
||||||
|
->orWhereNull('last_remembered_at')
|
||||||
|
);
|
||||||
|
foreach ($query->get() as $participant) {
|
||||||
|
if (count($participant->preventions()) === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mail::send(new PreventionRememberMail($participant));
|
||||||
|
|
||||||
|
$participant->update(['last_remembered_at' => now()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ class Form extends Model implements HasMedia
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
'is_private' => 'boolean',
|
'is_private' => 'boolean',
|
||||||
'export' => ExportData::class,
|
'export' => ExportData::class,
|
||||||
|
'needs_prevention' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var array<int, string> */
|
/** @var array<int, string> */
|
||||||
|
|
|
@ -6,14 +6,17 @@ use App\Form\Data\FieldCollection;
|
||||||
use App\Form\Data\FormConfigData;
|
use App\Form\Data\FormConfigData;
|
||||||
use App\Form\Mails\ConfirmRegistrationMail;
|
use App\Form\Mails\ConfirmRegistrationMail;
|
||||||
use App\Form\Scopes\ParticipantFilterScope;
|
use App\Form\Scopes\ParticipantFilterScope;
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Prevention\Contracts\Preventable;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
class Participant extends Model
|
class Participant extends Model implements Preventable
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
|
@ -21,6 +24,7 @@ class Participant extends Model
|
||||||
|
|
||||||
public $casts = [
|
public $casts = [
|
||||||
'data' => 'json',
|
'data' => 'json',
|
||||||
|
'last_remembered_at' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,6 +52,14 @@ class Participant extends Model
|
||||||
return $filter->apply($query);
|
return $filter->apply($query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsTo<Member, self>
|
||||||
|
*/
|
||||||
|
public function member(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Member::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function getFields(): FieldCollection
|
public function getFields(): FieldCollection
|
||||||
{
|
{
|
||||||
return FieldCollection::fromRequest($this->form, $this->data);
|
return FieldCollection::fromRequest($this->form, $this->data);
|
||||||
|
@ -70,6 +82,29 @@ class Participant extends Model
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mail::to($this->getFields()->getMailRecipient())->queue(new ConfirmRegistrationMail($this));
|
Mail::to($this->getMailRecipient())->queue(new ConfirmRegistrationMail($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function preventableLayout(): string
|
||||||
|
{
|
||||||
|
return 'mail.prevention.prevention-remember-participant';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function preventions(): array
|
||||||
|
{
|
||||||
|
return $this->member?->preventions($this->form->from) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMailRecipient(): stdClass
|
||||||
|
{
|
||||||
|
return $this->getFields()->getMailRecipient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function preventableSubject(): string
|
||||||
|
{
|
||||||
|
return 'Nachweise erforderlich für deine Anmeldung zu ' . $this->form->name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ class FormResource extends JsonResource
|
||||||
'is_private' => $this->is_private,
|
'is_private' => $this->is_private,
|
||||||
'has_nami_field' => $this->getFields()->hasNamiField(),
|
'has_nami_field' => $this->getFields()->hasNamiField(),
|
||||||
'export' => $this->export,
|
'export' => $this->export,
|
||||||
|
'needs_prevention' => $this->needs_prevention,
|
||||||
'links' => [
|
'links' => [
|
||||||
'participant_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => null]),
|
'participant_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => null]),
|
||||||
'participant_root_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => -1]),
|
'participant_root_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => -1]),
|
||||||
|
|
|
@ -23,7 +23,6 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Scout\Attributes\SearchUsingFullText;
|
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
use Sabre\VObject\Component\VCard;
|
use Sabre\VObject\Component\VCard;
|
||||||
use Sabre\VObject\Reader;
|
use Sabre\VObject\Reader;
|
||||||
|
@ -33,6 +32,7 @@ use Zoomyboy\Osm\Coordinate;
|
||||||
use Zoomyboy\Osm\Geolocatable;
|
use Zoomyboy\Osm\Geolocatable;
|
||||||
use Zoomyboy\Osm\HasGeolocation;
|
use Zoomyboy\Osm\HasGeolocation;
|
||||||
use Zoomyboy\Phone\HasPhoneNumbers;
|
use Zoomyboy\Phone\HasPhoneNumbers;
|
||||||
|
use App\Prevention\Enums\Prevention;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property string $subscription_name
|
* @property string $subscription_name
|
||||||
|
@ -341,6 +341,37 @@ class Member extends Model implements Geolocatable
|
||||||
return $query->where('bill_kind', '!=', null)->where('subscription_id', '!=', null);
|
return $query->where('bill_kind', '!=', null)->where('subscription_id', '!=', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, Prevention>
|
||||||
|
*/
|
||||||
|
public function preventions(?Carbon $date = null): array
|
||||||
|
{
|
||||||
|
$date = $date ?: now();
|
||||||
|
if (!$this->isLeader()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array<int, Prevention> */
|
||||||
|
$preventions = [];
|
||||||
|
|
||||||
|
if ($this->efz === null || $this->efz->diffInYears($date) >= 5) {
|
||||||
|
$preventions[] = Prevention::EFZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->more_ps_at === null) {
|
||||||
|
if ($this->ps_at === null || $this->ps_at->diffInYears($date) >= 5) {
|
||||||
|
$preventions[] = Prevention::PS;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($this->more_ps_at === null || $this->more_ps_at->diffInYears($date) >= 5) {
|
||||||
|
$preventions[] = Prevention::MOREPS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $preventions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Builder<self> $query
|
* @param Builder<self> $query
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention\Actions;
|
||||||
|
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class PreventionIndexAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
session()->put('menu', 'setting');
|
||||||
|
session()->put('title', 'Prävention');
|
||||||
|
|
||||||
|
return Inertia::render('setting/Prevention');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention\Actions;
|
||||||
|
|
||||||
|
use App\Prevention\PreventionSettings;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingApiAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'data' => app(PreventionSettings::class)->toArray(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention\Actions;
|
||||||
|
|
||||||
|
use App\Lib\Events\Succeeded;
|
||||||
|
use App\Prevention\PreventionSettings;
|
||||||
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class SettingStoreAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'formmail' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ActionRequest $request): void
|
||||||
|
{
|
||||||
|
app(PreventionSettings::class)->fill($request->validated())->save();
|
||||||
|
|
||||||
|
Succeeded::message('Einstellungen gespeichert.')->dispatch();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention\Contracts;
|
||||||
|
|
||||||
|
use App\Prevention\Enums\Prevention;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
interface Preventable
|
||||||
|
{
|
||||||
|
|
||||||
|
public function preventableLayout(): string;
|
||||||
|
public function preventableSubject(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, Prevention>
|
||||||
|
*/
|
||||||
|
public function preventions(): array;
|
||||||
|
|
||||||
|
public function getMailRecipient(): stdClass;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention\Enums;
|
||||||
|
|
||||||
|
enum Prevention
|
||||||
|
{
|
||||||
|
case EFZ;
|
||||||
|
case PS;
|
||||||
|
case MOREPS;
|
||||||
|
|
||||||
|
public function text(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
static::EFZ => 'erweitertes Führungszeugnis',
|
||||||
|
static::PS => 'Präventionsschulung Basis Plus',
|
||||||
|
static::MOREPS => 'Präventionsschulung (Auffrischung)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention\Mails;
|
||||||
|
|
||||||
|
use App\Invoice\InvoiceSettings;
|
||||||
|
use App\Prevention\Contracts\Preventable;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Mail\Attachment;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class PreventionRememberMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public InvoiceSettings $settings;
|
||||||
|
public string $documents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(public Preventable $preventable)
|
||||||
|
{
|
||||||
|
$this->settings = app(InvoiceSettings::class);
|
||||||
|
$this->documents = collect($preventable->preventions())->map(fn ($prevention) => "* {$prevention->text()}")->implode("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Mail\Mailables\Envelope
|
||||||
|
*/
|
||||||
|
public function envelope()
|
||||||
|
{
|
||||||
|
return (new Envelope(
|
||||||
|
subject: $this->preventable->preventableSubject(),
|
||||||
|
))->to($this->preventable->getMailRecipient()->email, $this->preventable->getMailRecipient()->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Mail\Mailables\Content
|
||||||
|
*/
|
||||||
|
public function content()
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
markdown: $this->preventable->preventableLayout(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Prevention;
|
||||||
|
|
||||||
|
use App\Prevention\Actions\PreventionIndexAction;
|
||||||
|
use App\Setting\Contracts\Indexable;
|
||||||
|
use App\Setting\LocalSettings;
|
||||||
|
|
||||||
|
class PreventionSettings extends LocalSettings implements Indexable
|
||||||
|
{
|
||||||
|
|
||||||
|
public array $formmail;
|
||||||
|
|
||||||
|
public static function group(): string
|
||||||
|
{
|
||||||
|
return 'prevention';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function slug(): string
|
||||||
|
{
|
||||||
|
return 'prevention';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function indexAction(): string
|
||||||
|
{
|
||||||
|
return PreventionIndexAction::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function title(): string
|
||||||
|
{
|
||||||
|
return 'Prävention';
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ use App\Form\FormSettings;
|
||||||
use App\Invoice\InvoiceSettings;
|
use App\Invoice\InvoiceSettings;
|
||||||
use App\Mailgateway\MailgatewaySettings;
|
use App\Mailgateway\MailgatewaySettings;
|
||||||
use App\Module\ModuleSettings;
|
use App\Module\ModuleSettings;
|
||||||
|
use App\Prevention\PreventionSettings;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class SettingServiceProvider extends ServiceProvider
|
class SettingServiceProvider extends ServiceProvider
|
||||||
|
@ -34,5 +35,6 @@ class SettingServiceProvider extends ServiceProvider
|
||||||
app(SettingFactory::class)->register(NamiSettings::class);
|
app(SettingFactory::class)->register(NamiSettings::class);
|
||||||
app(SettingFactory::class)->register(FormSettings::class);
|
app(SettingFactory::class)->register(FormSettings::class);
|
||||||
app(SettingFactory::class)->register(FileshareSettings::class);
|
app(SettingFactory::class)->register(FileshareSettings::class);
|
||||||
|
app(SettingFactory::class)->register(PreventionSettings::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Form\Models\Participant;
|
||||||
|
use App\Member\Member;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('participants', function (Blueprint $table) {
|
||||||
|
$table->datetime('last_remembered_at')->nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('forms', function (Blueprint $table) {
|
||||||
|
$table->boolean('needs_prevention')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
||||||
|
|
||||||
|
return new class extends SettingsMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->migrator->add('prevention.formmail', ['time' => 1, 'blocks' => []]);
|
||||||
|
}
|
||||||
|
};
|
|
@ -8,22 +8,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui-popup
|
<ui-popup v-if="condition !== null" heading="Bedingungen" @close="
|
||||||
v-if="condition !== null"
|
condition.resolve(condition.data);
|
||||||
heading="Bedingungen"
|
condition = null;
|
||||||
@close="
|
">
|
||||||
condition.resolve(condition.data);
|
|
||||||
condition = null;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<slot name="conditions" :data="condition.data" :resolve="condition.resolve" :reject="condition.reject"></slot>
|
<slot name="conditions" :data="condition.data" :resolve="condition.resolve" :reject="condition.reject"></slot>
|
||||||
</ui-popup>
|
</ui-popup>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {debounce} from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import {onMounted, ref} from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import EditorJS from '@editorjs/editorjs';
|
import EditorJS from '@editorjs/editorjs';
|
||||||
import Header from '@editorjs/header';
|
import Header from '@editorjs/header';
|
||||||
import Paragraph from '@editorjs/paragraph';
|
import Paragraph from '@editorjs/paragraph';
|
||||||
|
@ -32,7 +28,7 @@ import Alert from 'editorjs-alert';
|
||||||
import useFieldSize from '../../composables/useFieldSize.js';
|
import useFieldSize from '../../composables/useFieldSize.js';
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const {fieldAppearance, paddingX, paddingY, sizeClass} = useFieldSize();
|
const { fieldAppearance, paddingX, paddingY, sizeClass } = useFieldSize();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
required: {
|
required: {
|
||||||
|
@ -88,7 +84,7 @@ async function openPopup(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConditionTune {
|
class ConditionTune {
|
||||||
constructor({api, data, config, block}) {
|
constructor({ api, data, config, block }) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.data = data || {
|
this.data = data || {
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
<f-text id="name" v-model="single.name" class="grow" label="Name" required></f-text>
|
<f-text id="name" v-model="single.name" class="grow" label="Name" required></f-text>
|
||||||
<f-switch id="is_active" v-model="single.is_active" name="is_active" label="Aktiv"></f-switch>
|
<f-switch id="is_active" v-model="single.is_active" name="is_active" label="Aktiv"></f-switch>
|
||||||
<f-switch id="is_private" v-model="single.is_private" name="is_private" label="Privat"></f-switch>
|
<f-switch id="is_private" v-model="single.is_private" name="is_private" label="Privat"></f-switch>
|
||||||
|
<f-switch id="needs_prevention" v-model="single.needs_prevention" name="needs_prevention" label="Prävention"></f-switch>
|
||||||
</div>
|
</div>
|
||||||
<f-singlefile
|
<f-singlefile
|
||||||
id="header_image"
|
id="header_image"
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<page-layout>
|
||||||
|
<template #right>
|
||||||
|
<f-save-button form="preventionform"></f-save-button>
|
||||||
|
</template>
|
||||||
|
<setting-layout v-if="loaded">
|
||||||
|
<form id="preventionform" class="grow p-6" @submit.prevent="submit">
|
||||||
|
<div class="col-span-full text-gray-100 mb-3">
|
||||||
|
<p class="text-sm">Hier kannst du Einstellungen zu Prävention setzen.</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-4 mt-2">
|
||||||
|
<f-editor id="frommail" v-model="data.formmail" label="E-Mail für Veranstaltungs-TN"></f-editor>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</setting-layout>
|
||||||
|
</page-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useApiIndex } from '../../composables/useApiIndex.js';
|
||||||
|
import SettingLayout from '../setting/Layout.vue';
|
||||||
|
|
||||||
|
const { axios, data, reload } = useApiIndex('/api/prevention', 'prevention');
|
||||||
|
const loaded = ref(false);
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
await reload();
|
||||||
|
loaded.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
await axios.post('/api/prevention', { ...data.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
</script>
|
|
@ -0,0 +1,17 @@
|
||||||
|
@component('mail::message')
|
||||||
|
# Hallo {{ $preventable->member->fullname }},
|
||||||
|
|
||||||
|
Du hast dich für die Veranstaltung __{{$preventable->form->name}}__ angemeldet.
|
||||||
|
|
||||||
|
Damit du an der Veranstaltung als leitende oder helfende Person teilnehmen kannst, ist noch folgendes einzureichen oder zu beachten.
|
||||||
|
|
||||||
|
{!! $documents !!}
|
||||||
|
|
||||||
|
@component('mail::subcopy')
|
||||||
|
|
||||||
|
Herzliche Grüße und gut Pfad
|
||||||
|
|
||||||
|
{{$settings->from_long}}
|
||||||
|
@endcomponent
|
||||||
|
|
||||||
|
@endcomponent
|
|
@ -3,9 +3,13 @@
|
||||||
use App\Contribution\Actions\GenerateApiAction as ContributionGenerateApiAction;
|
use App\Contribution\Actions\GenerateApiAction as ContributionGenerateApiAction;
|
||||||
use App\Form\Actions\FormApiListAction;
|
use App\Form\Actions\FormApiListAction;
|
||||||
use App\Form\Actions\RegisterAction;
|
use App\Form\Actions\RegisterAction;
|
||||||
|
use App\Prevention\Actions\SettingStoreAction as PreventionStoreAction;
|
||||||
use App\Group\Actions\GroupApiIndexAction;
|
use App\Group\Actions\GroupApiIndexAction;
|
||||||
|
use App\Prevention\Actions\SettingApiAction;
|
||||||
|
|
||||||
Route::post('/contribution-generate', ContributionGenerateApiAction::class)->name('api.contribution.generate')->middleware('client:contribution-generate');
|
Route::post('/contribution-generate', ContributionGenerateApiAction::class)->name('api.contribution.generate')->middleware('client:contribution-generate');
|
||||||
Route::post('/form/{form}/register', RegisterAction::class)->name('form.register');
|
Route::post('/form/{form}/register', RegisterAction::class)->name('form.register');
|
||||||
Route::get('/group/{group?}', GroupApiIndexAction::class)->name('api.group');
|
Route::get('/group/{group?}', GroupApiIndexAction::class)->name('api.group');
|
||||||
Route::get('/form', FormApiListAction::class)->name('api.form.index');
|
Route::get('/form', FormApiListAction::class)->name('api.form.index');
|
||||||
|
Route::get('/prevention', SettingApiAction::class)->name('api.prevention.index');
|
||||||
|
Route::post('/prevention', PreventionStoreAction::class)->name('api.prevention.store');
|
||||||
|
|
|
@ -52,6 +52,7 @@ class FormIndexActionTest extends FormTestCase
|
||||||
->assertInertiaPath('data.data.0.is_active', true)
|
->assertInertiaPath('data.data.0.is_active', true)
|
||||||
->assertInertiaPath('data.data.0.is_private', false)
|
->assertInertiaPath('data.data.0.is_private', false)
|
||||||
->assertInertiaPath('data.data.0.registration_from', '2023-05-06 04:00:00')
|
->assertInertiaPath('data.data.0.registration_from', '2023-05-06 04:00:00')
|
||||||
|
->assertInertiaPath('data.data.0.needs_prevention', false)
|
||||||
->assertInertiaPath('data.data.0.registration_until', '2023-04-01 05:00:00')
|
->assertInertiaPath('data.data.0.registration_until', '2023-04-01 05:00:00')
|
||||||
->assertInertiaPath('data.data.0.links.participant_index', route('form.participant.index', ['form' => $form]))
|
->assertInertiaPath('data.data.0.links.participant_index', route('form.participant.index', ['form' => $form]))
|
||||||
->assertInertiaPath('data.data.0.links.export', route('form.export', ['form' => $form]))
|
->assertInertiaPath('data.data.0.links.export', route('form.export', ['form' => $form]))
|
||||||
|
|
|
@ -49,6 +49,7 @@ class FormRequest extends RequestFactory
|
||||||
'header_image' => $this->getHeaderImagePayload(str()->uuid() . '.jpg'),
|
'header_image' => $this->getHeaderImagePayload(str()->uuid() . '.jpg'),
|
||||||
'mailattachments' => [],
|
'mailattachments' => [],
|
||||||
'export' => ExportData::from([])->toArray(),
|
'export' => ExportData::from([])->toArray(),
|
||||||
|
'needs_prevention' => $this->faker->boolean(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ use App\Fileshare\Data\FileshareResourceData;
|
||||||
use App\Form\Data\ExportData;
|
use App\Form\Data\ExportData;
|
||||||
use App\Form\Models\Form;
|
use App\Form\Models\Form;
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
|
|
||||||
class FormUpdateActionTest extends FormTestCase
|
class FormUpdateActionTest extends FormTestCase
|
||||||
{
|
{
|
||||||
|
@ -124,4 +123,14 @@ class FormUpdateActionTest extends FormTestCase
|
||||||
$this->patchJson(route('form.update', ['form' => $form]), $payload)->assertSessionDoesntHaveErrors()->assertOk();
|
$this->patchJson(route('form.update', ['form' => $form]), $payload)->assertSessionDoesntHaveErrors()->assertOk();
|
||||||
$this->assertEquals(['firstname', 'geb', 'lastname'], $form->fresh()->meta['active_columns']);
|
$this->assertEquals(['firstname', 'geb', 'lastname'], $form->fresh()->meta['active_columns']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testItUpdatesPrevention(): void
|
||||||
|
{
|
||||||
|
$this->login()->loginNami()->withoutExceptionHandling();
|
||||||
|
$form = Form::factory()->create();
|
||||||
|
$payload = FormRequest::new()->state(['needs_prevention' => true])->create();
|
||||||
|
|
||||||
|
$this->patchJson(route('form.update', ['form' => $form]), $payload);
|
||||||
|
$this->assertTrue($form->fresh()->needs_prevention);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Member;
|
||||||
|
|
||||||
|
use App\Prevention\Enums\Prevention;
|
||||||
|
use App\Form\Actions\PreventionRememberAction;
|
||||||
|
use App\Form\Enums\NamiType;
|
||||||
|
use App\Form\Models\Form;
|
||||||
|
use App\Form\Models\Participant;
|
||||||
|
use App\Invoice\InvoiceSettings;
|
||||||
|
use App\Prevention\Mails\PreventionRememberMail;
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use Generator;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Tests\Lib\CreatesFormFields;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PreventionTest extends TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
use DatabaseTransactions;
|
||||||
|
use CreatesFormFields;
|
||||||
|
|
||||||
|
public function testItRemembersWhenNotRememberedYet(): void
|
||||||
|
{
|
||||||
|
Mail::fake();
|
||||||
|
$form = $this->createForm();
|
||||||
|
$participant = $this->createParticipant($form);
|
||||||
|
|
||||||
|
PreventionRememberAction::run();
|
||||||
|
|
||||||
|
$this->assertEquals(now()->format('Y-m-d'), $participant->fresh()->last_remembered_at->format('Y-m-d'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRemembersWhenRememberIsDue(): void
|
||||||
|
{
|
||||||
|
Mail::fake();
|
||||||
|
$form = $this->createForm();
|
||||||
|
$participant = tap($this->createParticipant($form), fn ($p) => $p->update(['last_remembered_at' => now()->subWeeks(3)]));
|
||||||
|
|
||||||
|
PreventionRememberAction::run();
|
||||||
|
|
||||||
|
$this->assertEquals(now()->format('Y-m-d'), $participant->fresh()->last_remembered_at->format('Y-m-d'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntRememberWhenRememberingIsNotDue(): void
|
||||||
|
{
|
||||||
|
Mail::fake();
|
||||||
|
$form = $this->createForm();
|
||||||
|
$participant = tap($this->createParticipant($form), fn ($p) => $p->update(['last_remembered_at' => now()->subWeeks(1)]));
|
||||||
|
|
||||||
|
PreventionRememberAction::run();
|
||||||
|
|
||||||
|
$this->assertEquals(now()->subWeeks(1)->format('Y-m-d'), $participant->fresh()->last_remembered_at->format('Y-m-d'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntRememberWhenFormDoesntNeedPrevention(): void
|
||||||
|
{
|
||||||
|
Mail::fake();
|
||||||
|
$form = tap($this->createForm(), fn ($form) => $form->update(['needs_prevention' => false]));
|
||||||
|
$participant = $this->createParticipant($form);
|
||||||
|
|
||||||
|
PreventionRememberAction::run();
|
||||||
|
|
||||||
|
$this->assertNull($participant->fresh()->last_remembered_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntRememberWhenParticipantDoesntHaveMember(): void
|
||||||
|
{
|
||||||
|
Mail::fake();
|
||||||
|
$form = $this->createForm();
|
||||||
|
$participant = $this->createParticipant($form);
|
||||||
|
$participant->member->delete();
|
||||||
|
|
||||||
|
PreventionRememberAction::run();
|
||||||
|
|
||||||
|
$this->assertNull($participant->fresh()->last_remembered_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntRememberWhenMemberIsNotALeader(): void
|
||||||
|
{
|
||||||
|
Mail::fake();
|
||||||
|
$form = $this->createForm();
|
||||||
|
$participant = $this->createParticipant($form);
|
||||||
|
$participant->member->memberships->each->delete();
|
||||||
|
|
||||||
|
PreventionRememberAction::run();
|
||||||
|
|
||||||
|
$this->assertNull($participant->fresh()->last_remembered_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function attributes(): Generator
|
||||||
|
{
|
||||||
|
yield [
|
||||||
|
'attrs' => ['efz' => null, 'ps_at' => now()],
|
||||||
|
'preventions' => [Prevention::EFZ]
|
||||||
|
];
|
||||||
|
|
||||||
|
yield [
|
||||||
|
'attrs' => ['efz' => now(), 'ps_at' => null],
|
||||||
|
'preventions' => [Prevention::PS]
|
||||||
|
];
|
||||||
|
|
||||||
|
yield [
|
||||||
|
'attrs' => ['efz' => now()->subDay(), 'ps_at' => now()],
|
||||||
|
'preventions' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
yield [
|
||||||
|
'attrs' => ['efz' => now(), 'ps_at' => now()->subDay()],
|
||||||
|
'preventions' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
yield [
|
||||||
|
'attrs' => ['efz' => now()->subYears(5)->subDay(), 'ps_at' => now()],
|
||||||
|
'preventions' => [Prevention::EFZ]
|
||||||
|
];
|
||||||
|
|
||||||
|
yield [
|
||||||
|
'attrs' => ['efz' => now(), 'ps_at' => now()->subYears(5)->subDay()],
|
||||||
|
'preventions' => [Prevention::PS]
|
||||||
|
];
|
||||||
|
|
||||||
|
yield [
|
||||||
|
'attrs' => ['efz' => now(), 'ps_at' => now()->subYears(5)->subDay(), 'more_ps_at' => now()],
|
||||||
|
'preventions' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
yield [
|
||||||
|
'attrs' => ['efz' => now(), 'ps_at' => now()->subYears(15), 'more_ps_at' => now()->subYears(5)->subDay()],
|
||||||
|
'preventions' => [Prevention::MOREPS],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, Prevention> $preventions
|
||||||
|
* @param array<string, mixed> $memberAttributes
|
||||||
|
* @dataProvider attributes
|
||||||
|
*/
|
||||||
|
public function testItRemembersMember(array $memberAttributes, array $preventions): void
|
||||||
|
{
|
||||||
|
Mail::fake();
|
||||||
|
$form = $this->createForm();
|
||||||
|
$participant = $this->createParticipant($form);
|
||||||
|
$participant->member->update($memberAttributes);
|
||||||
|
|
||||||
|
PreventionRememberAction::run();
|
||||||
|
|
||||||
|
if (count($preventions)) {
|
||||||
|
Mail::assertSent(PreventionRememberMail::class, fn ($mail) => $mail->preventable->preventions() === $preventions);
|
||||||
|
$this->assertNotNull($participant->fresh()->last_remembered_at);
|
||||||
|
} else {
|
||||||
|
Mail::assertNotSent(PreventionRememberMail::class);
|
||||||
|
$this->assertNull($participant->fresh()->last_remembered_at);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRendersMail(): void
|
||||||
|
{
|
||||||
|
InvoiceSettings::fake(['from_long' => 'Stamm Beispiel']);
|
||||||
|
$form = $this->createForm();
|
||||||
|
$participant = $this->createParticipant($form);
|
||||||
|
(new PreventionRememberMail($participant))
|
||||||
|
->assertSeeInText($participant->member->firstname)
|
||||||
|
->assertSeeInText($participant->form->name)
|
||||||
|
->assertSeeInText('erweitertes Führungszeugnis')
|
||||||
|
->assertSeeInText('Stamm Beispiel')
|
||||||
|
->assertSeeInText($participant->member->lastname);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createForm(): Form
|
||||||
|
{
|
||||||
|
return Form::factory()->fields([
|
||||||
|
$this->textField('vorname')->namiType(NamiType::FIRSTNAME),
|
||||||
|
])->create(['needs_prevention' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createParticipant(Form $form): Participant
|
||||||
|
{
|
||||||
|
return Participant::factory()->for($form)->data(['vorname' => 'Max'])->for(Member::factory()->defaults()->has(Membership::factory()->inLocal('€ LeiterIn', 'Wölfling')))->create();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Prevention;
|
||||||
|
|
||||||
|
use App\Prevention\PreventionSettings;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Tests\RequestFactories\EditorRequestFactory;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class SettingTest extends TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
use DatabaseTransactions;
|
||||||
|
|
||||||
|
public function testItOpensSettingsPage(): void
|
||||||
|
{
|
||||||
|
$this->login()->loginNami();
|
||||||
|
|
||||||
|
$this->get('/setting/prevention')->assertComponent('prevention/Index')->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItReceivesSettings(): void
|
||||||
|
{
|
||||||
|
$this->login()->loginNami();
|
||||||
|
|
||||||
|
$text = EditorRequestFactory::new()->text(50, 'lorem ipsum')->create();
|
||||||
|
app(PreventionSettings::class)->fill(['formmail' => $text])->save();
|
||||||
|
|
||||||
|
$this->get('/api/prevention')
|
||||||
|
->assertJsonPath('data.formmail.blocks.0.data.text', 'lorem ipsum');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItStoresSettings(): void
|
||||||
|
{
|
||||||
|
$this->login()->loginNami();
|
||||||
|
|
||||||
|
$text = EditorRequestFactory::new()->text(50, 'new lorem')->create();
|
||||||
|
|
||||||
|
$this->post('/api/prevention', ['formmail' => $text])->assertOk();
|
||||||
|
$this->assertEquals($text, app(PreventionSettings::class)->formmail);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue