Compare commits
7 Commits
738128a4f4
...
dbde189575
Author | SHA1 | Date |
---|---|---|
philipp lang | dbde189575 | |
philipp lang | 146cbb0bef | |
philipp lang | e6477fff2b | |
philipp lang | 3c725c0efc | |
philipp lang | f632a8bb8f | |
philipp lang | 2f03dccebb | |
philipp lang | 235f439d51 |
|
@ -37,6 +37,7 @@ class FormStoreAction
|
||||||
'export' => 'nullable|array',
|
'export' => 'nullable|array',
|
||||||
'needs_prevention' => 'present|boolean',
|
'needs_prevention' => 'present|boolean',
|
||||||
'prevention_text' => 'array',
|
'prevention_text' => 'array',
|
||||||
|
'prevention_conditions' => 'array',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Form\Actions;
|
namespace App\Form\Actions;
|
||||||
|
|
||||||
use App\Form\Models\Form;
|
use App\Form\Models\Form;
|
||||||
|
use App\Lib\Editor\Condition;
|
||||||
use App\Lib\Events\Succeeded;
|
use App\Lib\Events\Succeeded;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
@ -36,6 +37,7 @@ class FormUpdateAction
|
||||||
'export' => 'nullable|array',
|
'export' => 'nullable|array',
|
||||||
'needs_prevention' => 'present|boolean',
|
'needs_prevention' => 'present|boolean',
|
||||||
'prevention_text' => 'array',
|
'prevention_text' => 'array',
|
||||||
|
'prevention_conditions' => 'array',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Form\Actions;
|
namespace App\Form\Actions;
|
||||||
|
|
||||||
|
use App\Form\Editor\FormConditionResolver;
|
||||||
use App\Form\Models\Participant;
|
use App\Form\Models\Participant;
|
||||||
use App\Prevention\Mails\PreventionRememberMail;
|
use App\Prevention\Mails\PreventionRememberMail;
|
||||||
use App\Prevention\PreventionSettings;
|
use App\Prevention\PreventionSettings;
|
||||||
|
@ -23,6 +24,10 @@ class PreventionRememberAction
|
||||||
->orWhereNull('last_remembered_at')
|
->orWhereNull('last_remembered_at')
|
||||||
);
|
);
|
||||||
foreach ($query->get() as $participant) {
|
foreach ($query->get() as $participant) {
|
||||||
|
if (!app(FormConditionResolver::class)->forParticipant($participant)->filterCondition($participant->form->prevention_conditions)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($participant->getFields()->getMailRecipient() === null || count($participant->preventions()) === 0) {
|
if ($participant->getFields()->getMailRecipient() === null || count($participant->preventions()) === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Form\Editor;
|
namespace App\Form\Editor;
|
||||||
|
|
||||||
use App\Form\Models\Participant;
|
use App\Form\Models\Participant;
|
||||||
|
use App\Lib\Editor\Condition;
|
||||||
use App\Lib\Editor\ConditionResolver;
|
use App\Lib\Editor\ConditionResolver;
|
||||||
|
|
||||||
class FormConditionResolver extends ConditionResolver
|
class FormConditionResolver extends ConditionResolver
|
||||||
|
@ -20,24 +21,24 @@ class FormConditionResolver extends ConditionResolver
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
public function filterCondition(string $mode, array $ifs): bool
|
public function filterCondition(Condition $condition): bool
|
||||||
{
|
{
|
||||||
if (count($ifs) === 0) {
|
if (!$condition->hasStatements()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($ifs as $if) {
|
foreach ($condition->ifs as $if) {
|
||||||
$field = $this->participant->getFields()->findByKey($if['field']);
|
$field = $this->participant->getFields()->findByKey($if->field);
|
||||||
$matches = $field->matches($if['comparator'], $if['value']);
|
$matches = $field->matches($if->comparator, $if->value);
|
||||||
if ($matches && $mode === 'any') {
|
if ($matches && $condition->isAny()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!$matches && $mode === 'all') {
|
if (!$matches && $condition->isAll()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($mode === 'any') {
|
if ($condition->isAny()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ use App\Form\Models\Form;
|
||||||
use App\Form\Models\Participant;
|
use App\Form\Models\Participant;
|
||||||
use App\Form\Presenters\DefaultPresenter;
|
use App\Form\Presenters\DefaultPresenter;
|
||||||
use App\Form\Presenters\Presenter;
|
use App\Form\Presenters\Presenter;
|
||||||
|
use App\Lib\Editor\Comparator;
|
||||||
use Faker\Generator;
|
use Faker\Generator;
|
||||||
use Spatie\LaravelData\Data;
|
use Spatie\LaravelData\Data;
|
||||||
use Spatie\LaravelData\Attributes\MapInputName;
|
use Spatie\LaravelData\Attributes\MapInputName;
|
||||||
|
@ -170,7 +171,7 @@ abstract class Field extends Data
|
||||||
return $this->key . '_display';
|
return $this->key . '_display';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function matches(string $comparator, mixed $value): bool
|
public function matches(Comparator $comparator, mixed $value): bool
|
||||||
{
|
{
|
||||||
return $this->getMatcher()->setValue($this->value)->matches($comparator, $value);
|
return $this->getMatcher()->setValue($this->value)->matches($comparator, $value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Form\Mails;
|
||||||
use App\Form\Data\FormConfigData;
|
use App\Form\Data\FormConfigData;
|
||||||
use App\Form\Editor\FormConditionResolver;
|
use App\Form\Editor\FormConditionResolver;
|
||||||
use App\Form\Models\Participant;
|
use App\Form\Models\Participant;
|
||||||
|
use App\Lib\Editor\Condition;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Mail\Attachment;
|
use Illuminate\Mail\Attachment;
|
||||||
use Illuminate\Mail\Mailable;
|
use Illuminate\Mail\Mailable;
|
||||||
|
@ -71,10 +72,7 @@ class ConfirmRegistrationMail extends Mailable
|
||||||
$conditionResolver = app(FormConditionResolver::class)->forParticipant($this->participant);
|
$conditionResolver = app(FormConditionResolver::class)->forParticipant($this->participant);
|
||||||
|
|
||||||
return $this->participant->form->getMedia('mailattachments')
|
return $this->participant->form->getMedia('mailattachments')
|
||||||
->filter(fn ($media) => $conditionResolver->filterCondition(
|
->filter(fn ($media) => $conditionResolver->filterCondition(Condition::fromMedia($media)))
|
||||||
data_get($media->getCustomProperty('conditions'), 'mode', 'all'),
|
|
||||||
data_get($media->getCustomProperty('conditions'), 'ifs', []),
|
|
||||||
))
|
|
||||||
->map(fn ($media) => Attachment::fromStorageDisk($media->disk, $media->getPathRelativeToRoot()))
|
->map(fn ($media) => Attachment::fromStorageDisk($media->disk, $media->getPathRelativeToRoot()))
|
||||||
->all();
|
->all();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace App\Form\Matchers;
|
namespace App\Form\Matchers;
|
||||||
|
|
||||||
|
use App\Lib\Editor\Comparator;
|
||||||
|
|
||||||
abstract class Matcher
|
abstract class Matcher
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -14,5 +16,5 @@ abstract class Matcher
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract public function matches(string $comparator, mixed $value): bool;
|
abstract public function matches(Comparator $comparator, mixed $value): bool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,26 @@
|
||||||
|
|
||||||
namespace App\Form\Matchers;
|
namespace App\Form\Matchers;
|
||||||
|
|
||||||
|
use App\Lib\Editor\Comparator;
|
||||||
|
|
||||||
class SingleValueMatcher extends Matcher
|
class SingleValueMatcher extends Matcher
|
||||||
{
|
{
|
||||||
|
|
||||||
public function matches(string $comparator, mixed $value): bool
|
public function matches(Comparator $comparator, mixed $value): bool
|
||||||
{
|
{
|
||||||
if ($comparator === 'isEqual' && $value === $this->value) {
|
if ($comparator === Comparator::EQUAL && $value === $this->value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($comparator === 'isNotEqual' && $value !== $this->value) {
|
if ($comparator === Comparator::NOTEQUAL && $value !== $this->value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($comparator === 'isIn' && in_array($this->value, $value)) {
|
if ($comparator === Comparator::IN && in_array($this->value, $value)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($comparator === 'isNotIn' && !in_array($this->value, $value)) {
|
if ($comparator === Comparator::NOTIN && !in_array($this->value, $value)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Form\Models;
|
||||||
use App\Form\Data\ExportData;
|
use App\Form\Data\ExportData;
|
||||||
use App\Form\Data\FieldCollection;
|
use App\Form\Data\FieldCollection;
|
||||||
use App\Form\Data\FormConfigData;
|
use App\Form\Data\FormConfigData;
|
||||||
|
use App\Lib\Editor\Condition;
|
||||||
use App\Lib\Editor\EditorData;
|
use App\Lib\Editor\EditorData;
|
||||||
use Cviebrock\EloquentSluggable\Sluggable;
|
use Cviebrock\EloquentSluggable\Sluggable;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
@ -39,6 +40,7 @@ class Form extends Model implements HasMedia
|
||||||
'export' => ExportData::class,
|
'export' => ExportData::class,
|
||||||
'needs_prevention' => 'boolean',
|
'needs_prevention' => 'boolean',
|
||||||
'prevention_text' => EditorData::class,
|
'prevention_text' => EditorData::class,
|
||||||
|
'prevention_conditions' => Condition::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var array<int, string> */
|
/** @var array<int, string> */
|
||||||
|
|
|
@ -52,6 +52,7 @@ class FormResource extends JsonResource
|
||||||
'export' => $this->export,
|
'export' => $this->export,
|
||||||
'needs_prevention' => $this->needs_prevention,
|
'needs_prevention' => $this->needs_prevention,
|
||||||
'prevention_text' => $this->prevention_text,
|
'prevention_text' => $this->prevention_text,
|
||||||
|
'prevention_conditions' => $this->prevention_conditions,
|
||||||
'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]),
|
||||||
|
@ -99,6 +100,7 @@ class FormResource extends JsonResource
|
||||||
'prevention_text' => EditorData::default(),
|
'prevention_text' => EditorData::default(),
|
||||||
'id' => null,
|
'id' => null,
|
||||||
'export' => ExportData::from([]),
|
'export' => ExportData::from([]),
|
||||||
|
'prevention_conditions' => ['mode' => 'all', 'ifs' => []],
|
||||||
],
|
],
|
||||||
'section_default' => [
|
'section_default' => [
|
||||||
'name' => '',
|
'name' => '',
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Editor;
|
||||||
|
|
||||||
|
enum Comparator: string
|
||||||
|
{
|
||||||
|
case EQUAL = 'isEqual';
|
||||||
|
case NOTEQUAL = 'isNotEqual';
|
||||||
|
case IN = 'isIn';
|
||||||
|
case NOTIN = 'isNotIn';
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Editor;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
use Spatie\LaravelData\DataCollection;
|
||||||
|
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||||
|
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||||
|
|
||||||
|
class Condition extends Data
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @param DataCollection<int, Statement> $ifs */
|
||||||
|
public function __construct(
|
||||||
|
public ConditionMode $mode,
|
||||||
|
#[DataCollectionOf(Statement::class)]
|
||||||
|
public DataCollection $ifs,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromMedia(Media $media): self
|
||||||
|
{
|
||||||
|
return $media->getCustomProperty('conditions') ? static::withoutMagicalCreationFrom($media->getCustomProperty('conditions')) : static::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function defaults(): self
|
||||||
|
{
|
||||||
|
return static::withoutMagicalCreationFrom(['mode' => 'any', 'ifs' => []]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasStatements(): bool
|
||||||
|
{
|
||||||
|
return count($this->ifs) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAny(): bool
|
||||||
|
{
|
||||||
|
return $this->mode === ConditionMode::ANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAll(): bool
|
||||||
|
{
|
||||||
|
return $this->mode === ConditionMode::ALL;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Editor;
|
||||||
|
|
||||||
|
enum ConditionMode: string
|
||||||
|
{
|
||||||
|
case ALL = 'all';
|
||||||
|
case ANY = 'any';
|
||||||
|
}
|
|
@ -5,10 +5,7 @@ namespace App\Lib\Editor;
|
||||||
abstract class ConditionResolver
|
abstract class ConditionResolver
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
abstract public function filterCondition(Condition $condition): bool;
|
||||||
* @param array<string, mixed> $ifs
|
|
||||||
*/
|
|
||||||
abstract public function filterCondition(string $mode, array $ifs): bool;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $content
|
* @param array<string, mixed> $content
|
||||||
|
@ -24,9 +21,9 @@ abstract class ConditionResolver
|
||||||
*/
|
*/
|
||||||
public function filterBlock(array $block): bool
|
public function filterBlock(array $block): bool
|
||||||
{
|
{
|
||||||
$mode = data_get($block, 'tunes.condition.mode', 'any');
|
return $this->filterCondition(Condition::withoutMagicalCreationFrom([
|
||||||
$ifs = data_get($block, 'tunes.condition.ifs', []);
|
'mode' => data_get($block, 'tunes.condition.mode', 'any'),
|
||||||
|
'ifs' => data_get($block, 'tunes.condition.ifs', []),
|
||||||
return $this->filterCondition($mode, $ifs);
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Lib\Editor;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
class Statement extends Data
|
||||||
|
{
|
||||||
|
/** @param mixed $value */
|
||||||
|
public function __construct(public string $field, public $value, public Comparator $comparator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -347,9 +347,6 @@ class Member extends Model implements Geolocatable
|
||||||
public function preventions(?Carbon $date = null): array
|
public function preventions(?Carbon $date = null): array
|
||||||
{
|
{
|
||||||
$date = $date ?: now();
|
$date = $date ?: now();
|
||||||
if (!$this->isLeader()) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var array<int, Prevention> */
|
/** @var array<int, Prevention> */
|
||||||
$preventions = [];
|
$preventions = [];
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Database\Factories\Form\Models;
|
||||||
|
|
||||||
use App\Form\Data\ExportData;
|
use App\Form\Data\ExportData;
|
||||||
use App\Form\Models\Form;
|
use App\Form\Models\Form;
|
||||||
|
use App\Lib\Editor\Condition;
|
||||||
use Database\Factories\Traits\FakesMedia;
|
use Database\Factories\Traits\FakesMedia;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
use Tests\Feature\Form\FormtemplateFieldRequest;
|
use Tests\Feature\Form\FormtemplateFieldRequest;
|
||||||
|
@ -54,6 +55,7 @@ class FormFactory extends Factory
|
||||||
'is_active' => true,
|
'is_active' => true,
|
||||||
'is_private' => false,
|
'is_private' => false,
|
||||||
'export' => ExportData::from([]),
|
'export' => ExportData::from([]),
|
||||||
|
'prevention_conditions' => Condition::defaults(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
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('forms', function (Blueprint $table) {
|
||||||
|
$table->json('prevention_conditions')->default(json_encode(['mode' => 'all', 'ifs' => []]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('forms', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('prevention_conditions');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -1 +1 @@
|
||||||
Subproject commit bd4118040fd2643b71f7f152ca2668f2097820a2
|
Subproject commit 25d36a53ce5bc10637773513185935503503d41c
|
|
@ -5,68 +5,72 @@
|
||||||
</ui-note>
|
</ui-note>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<f-select id="mode" v-model="inner.mode" :options="modeOptions" name="mode" label="Modus"></f-select>
|
<f-select :id="`mode-${id}`" :name="`mode-${id}`" :model-value="modelValue.mode" :options="modeOptions" label="Modus" @update:model-value="changeMode"></f-select>
|
||||||
|
|
||||||
<ui-icon-button class="mt-4 mb-2" icon="plus" @click="addCondition">Bedingung einfügen</ui-icon-button>
|
<ui-icon-button class="mt-4 mb-2" icon="plus" @click="addCondition">Bedingung einfügen</ui-icon-button>
|
||||||
|
|
||||||
<div v-for="(condition, index) in inner.ifs" :key="index" class="grid grid-cols-[1fr_1fr_1fr_max-content] gap-2">
|
<div v-for="(condition, index) in modelValue.ifs" :key="index" class="grid grid-cols-[1fr_1fr_1fr_max-content] gap-2">
|
||||||
<f-select
|
<f-select
|
||||||
:id="`field-${index}`"
|
:id="`field-${index}-${id}`"
|
||||||
:model-value="condition.field"
|
:model-value="condition.field"
|
||||||
:options="fieldOptions"
|
:options="fieldOptions"
|
||||||
:name="`field-${index}`"
|
:name="`field-${index}-${id}`"
|
||||||
label="Feld"
|
label="Feld"
|
||||||
@update:model-value="update(index, 'field', $event)"
|
@update:model-value="update(index, 'field', $event)"
|
||||||
></f-select>
|
></f-select>
|
||||||
<f-select
|
<f-select
|
||||||
:id="`comparator-${index}`"
|
:id="`comparator-${index}-${id}`"
|
||||||
:options="comparatorOptions"
|
:options="comparatorOptions"
|
||||||
:model-value="condition.comparator"
|
:model-value="condition.comparator"
|
||||||
:name="`comparator-${index}`"
|
:name="`comparator-${index}-${id}`"
|
||||||
label="Vergleich"
|
label="Vergleich"
|
||||||
@update:model-value="update(index, 'comparator', $event)"
|
@update:model-value="update(index, 'comparator', $event)"
|
||||||
></f-select>
|
></f-select>
|
||||||
<f-select
|
<f-select
|
||||||
v-if="condition.field && ['isEqual', 'isNotEqual'].includes(condition.comparator) && ['RadioField', 'DropdownField'].includes(getField(condition.field).type)"
|
v-if="condition.field && ['isEqual', 'isNotEqual'].includes(condition.comparator) && ['RadioField', 'DropdownField'].includes(getField(condition.field).type)"
|
||||||
:id="`value-${index}`"
|
:id="`value-${index}-${id}`"
|
||||||
v-model="condition.value"
|
v-model="condition.value"
|
||||||
:options="getOptions(condition.field)"
|
:options="getOptions(condition.field)"
|
||||||
:name="`value-${index}`"
|
:name="`value-${index}-${id}`"
|
||||||
label="Wert"
|
label="Wert"
|
||||||
></f-select>
|
></f-select>
|
||||||
<f-multipleselect
|
<f-multipleselect
|
||||||
v-if="condition.field && ['isIn', 'isNotIn'].includes(condition.comparator) && ['RadioField', 'DropdownField'].includes(getField(condition.field).type)"
|
v-if="condition.field && ['isIn', 'isNotIn'].includes(condition.comparator) && ['RadioField', 'DropdownField'].includes(getField(condition.field).type)"
|
||||||
:id="`value-${index}`"
|
:id="`value-${index}-${id}`"
|
||||||
v-model="condition.value"
|
v-model="condition.value"
|
||||||
:options="getOptions(condition.field)"
|
:options="getOptions(condition.field)"
|
||||||
label="Wert"
|
label="Wert"
|
||||||
></f-multipleselect>
|
></f-multipleselect>
|
||||||
<f-switch
|
<f-switch
|
||||||
v-if="condition.field && condition.comparator && ['CheckboxField'].includes(getField(condition.field).type)"
|
v-if="condition.field && condition.comparator && ['CheckboxField'].includes(getField(condition.field).type)"
|
||||||
:id="`value-${index}`"
|
:id="`value-${index}-${id}`"
|
||||||
v-model="condition.value"
|
v-model="condition.value"
|
||||||
:name="`value-${index}`"
|
:name="`value-${index}-${id}`"
|
||||||
label="Wert"
|
label="Wert"
|
||||||
></f-switch>
|
></f-switch>
|
||||||
<ui-action-button tooltip="Löschen" icon="trash" class="btn-danger self-end h-8" @click="inner.ifs.splice(index, 1)"></ui-action-button>
|
<ui-action-button tooltip="Löschen" icon="trash" class="btn-danger self-end h-8" @click="remove(index)"></ui-action-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui-icon-button class="mt-4 mb-2" icon="save" @click="save">Speichern</ui-icon-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js" setup>
|
<script lang="js" setup>
|
||||||
import { ref, inject, computed } from 'vue';
|
import { ref, inject, computed } from 'vue';
|
||||||
const axios = inject('axios');
|
const axios = inject('axios');
|
||||||
const emit = defineEmits(['save']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: {
|
modelValue: {
|
||||||
required: true,
|
required: true,
|
||||||
|
type: Object,
|
||||||
},
|
},
|
||||||
single: {
|
single: {
|
||||||
required: true,
|
required: true,
|
||||||
|
type: Object,
|
||||||
},
|
},
|
||||||
|
id: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const comparatorOptions = ref([
|
const comparatorOptions = ref([
|
||||||
|
@ -94,23 +98,33 @@ const fields = computed(() => {
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function changeMode(mode) {
|
||||||
|
emit('update:modelValue', {...props.modelValue, mode: mode});
|
||||||
|
}
|
||||||
|
|
||||||
function update(index, key, value) {
|
function update(index, key, value) {
|
||||||
|
var inner = {...props.modelValue};
|
||||||
if (key === 'comparator') {
|
if (key === 'comparator') {
|
||||||
var old = inner.value.ifs[index];
|
var old = inner.ifs[index];
|
||||||
inner.value.ifs[index] = {
|
inner.ifs[index] = {
|
||||||
field: old.field,
|
field: old.field,
|
||||||
comparator: value,
|
comparator: value,
|
||||||
value: old.field ? comparatorOptions.value.find((c) => c.id === value).defaultValue[getField(old.field).type] : null,
|
value: old.field ? comparatorOptions.value.find((c) => c.id === value).defaultValue[getField(old.field).type] : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (key === 'field') {
|
if (key === 'field') {
|
||||||
var old = inner.value.ifs[index];
|
var old = inner.ifs[index];
|
||||||
inner.value.ifs[index] = {
|
inner.ifs[index] = {
|
||||||
field: value,
|
field: value,
|
||||||
comparator: null,
|
comparator: null,
|
||||||
value: null,
|
value: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
emit('update:modelValue', inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(index) {
|
||||||
|
emit('update:modelValue', {...props.modelValue, ifs: props.modelValue.ifs.toSpliced(index, 1)});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getField(fieldName) {
|
function getField(fieldName) {
|
||||||
|
@ -129,22 +143,19 @@ const fieldOptions = computed(() =>
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const inner = ref(JSON.parse(JSON.stringify(props.value)));
|
function addCondition() {
|
||||||
|
emit('update:modelValue', {...props.modelValue, ifs: [
|
||||||
|
...props.modelValue.ifs,
|
||||||
|
{
|
||||||
|
field: null,
|
||||||
|
comparator: null,
|
||||||
|
value: null,
|
||||||
|
}
|
||||||
|
]});
|
||||||
|
}
|
||||||
|
|
||||||
const locked = ref(false);
|
const locked = ref(false);
|
||||||
|
|
||||||
function addCondition() {
|
|
||||||
inner.value.ifs.push({
|
|
||||||
field: null,
|
|
||||||
comparator: null,
|
|
||||||
value: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function save() {
|
|
||||||
emit('save', inner.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkIfDirty() {
|
async function checkIfDirty() {
|
||||||
const response = await axios.post(props.single.links.is_dirty, { config: props.single.config });
|
const response = await axios.post(props.single.links.is_dirty, { config: props.single.config });
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<conditions :id="id" v-model="inner" :single="single"></conditions>
|
||||||
|
<ui-icon-button class="mt-4 mb-2" icon="save" @click="save">Speichern</ui-icon-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js" setup>
|
||||||
|
import { ref, inject, computed } from 'vue';
|
||||||
|
const emit = defineEmits(['save']);
|
||||||
|
import Conditions from './Conditions.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
single: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const inner = ref(JSON.parse(JSON.stringify(props.value)));
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
emit('save', inner.value);
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -81,12 +81,12 @@
|
||||||
<ui-tabs v-model="activeMailTab" :entries="mailTabs"></ui-tabs>
|
<ui-tabs v-model="activeMailTab" :entries="mailTabs"></ui-tabs>
|
||||||
<f-editor v-if="activeMailTab === 0" id="mail_top" v-model="single.mail_top" name="mail_top" label="E-Mail-Teil 1" :rows="8" conditions required>
|
<f-editor v-if="activeMailTab === 0" id="mail_top" v-model="single.mail_top" name="mail_top" label="E-Mail-Teil 1" :rows="8" conditions required>
|
||||||
<template #conditions="{data, resolve}">
|
<template #conditions="{data, resolve}">
|
||||||
<conditions :single="single" :value="data" @save="resolve"> </conditions>
|
<conditions-form id="mail_top_conditions" :single="single" :value="data" @save="resolve"> </conditions-form>
|
||||||
</template>
|
</template>
|
||||||
</f-editor>
|
</f-editor>
|
||||||
<f-editor v-if="activeMailTab === 1" id="mail_bottom" v-model="single.mail_bottom" name="mail_bottom" label="E-Mail-Teil 2" :rows="8" conditions required>
|
<f-editor v-if="activeMailTab === 1" id="mail_bottom" v-model="single.mail_bottom" name="mail_bottom" label="E-Mail-Teil 2" :rows="8" conditions required>
|
||||||
<template #conditions="{data, resolve}">
|
<template #conditions="{data, resolve}">
|
||||||
<conditions :single="single" :value="data" @save="resolve"> </conditions>
|
<conditions-form id="mail_bottom_conditions" :single="single" :value="data" @save="resolve"> </conditions-form>
|
||||||
</template>
|
</template>
|
||||||
</f-editor>
|
</f-editor>
|
||||||
</div>
|
</div>
|
||||||
|
@ -124,6 +124,9 @@
|
||||||
:rows="6"
|
:rows="6"
|
||||||
label="Präventions-Hinweis"
|
label="Präventions-Hinweis"
|
||||||
></f-editor>
|
></f-editor>
|
||||||
|
<ui-box heading="Bedingung für Präventions-Unterlagen">
|
||||||
|
<conditions id="prevention_conditions" v-model="single.prevention_conditions" :single="single"> </conditions>
|
||||||
|
</ui-box>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
@ -134,7 +137,7 @@
|
||||||
</ui-popup>
|
</ui-popup>
|
||||||
|
|
||||||
<ui-popup v-if="fileSettingPopup !== null" :heading="`Bedingungen für Datei ${fileSettingPopup.name}`" @close="fileSettingPopup = null">
|
<ui-popup v-if="fileSettingPopup !== null" :heading="`Bedingungen für Datei ${fileSettingPopup.name}`" @close="fileSettingPopup = null">
|
||||||
<conditions :single="single" :value="fileSettingPopup.properties.conditions" @save="saveFileConditions"> </conditions>
|
<conditions-form id="filesettings" :single="single" :value="fileSettingPopup.properties.conditions" @save="saveFileConditions"> </conditions-form>
|
||||||
</ui-popup>
|
</ui-popup>
|
||||||
|
|
||||||
<page-filter breakpoint="xl">
|
<page-filter breakpoint="xl">
|
||||||
|
@ -188,6 +191,7 @@ import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
|
||||||
import FormBuilder from '../formtemplate/FormBuilder.vue';
|
import FormBuilder from '../formtemplate/FormBuilder.vue';
|
||||||
import Participants from './Participants.vue';
|
import Participants from './Participants.vue';
|
||||||
import Conditions from './Conditions.vue';
|
import Conditions from './Conditions.vue';
|
||||||
|
import ConditionsForm from './ConditionsForm.vue';
|
||||||
import { useToast } from 'vue-toastification';
|
import { useToast } from 'vue-toastification';
|
||||||
|
|
||||||
const props = defineProps(indexProps);
|
const props = defineProps(indexProps);
|
||||||
|
|
|
@ -61,6 +61,7 @@ class FormIndexActionTest extends FormTestCase
|
||||||
->assertInertiaPath('data.meta.templates.0.name', 'tname')
|
->assertInertiaPath('data.meta.templates.0.name', 'tname')
|
||||||
->assertInertiaPath('data.meta.templates.0.config.sections.0.name', 'sname')
|
->assertInertiaPath('data.meta.templates.0.config.sections.0.name', 'sname')
|
||||||
->assertInertiaPath('data.meta.default.name', '')
|
->assertInertiaPath('data.meta.default.name', '')
|
||||||
|
->assertInertiaPath('data.meta.default.prevention_conditions', ['mode' => 'all', 'ifs' => []])
|
||||||
->assertInertiaPath('data.meta.default.prevention_text.version', '1.0')
|
->assertInertiaPath('data.meta.default.prevention_text.version', '1.0')
|
||||||
->assertInertiaPath('data.meta.default.description', [])
|
->assertInertiaPath('data.meta.default.description', [])
|
||||||
->assertInertiaPath('data.meta.default.excerpt', '')
|
->assertInertiaPath('data.meta.default.excerpt', '')
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Tests\Feature\Form;
|
namespace Tests\Feature\Form;
|
||||||
|
|
||||||
use App\Form\Data\ExportData;
|
use App\Form\Data\ExportData;
|
||||||
|
use App\Lib\Editor\Condition;
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Tests\RequestFactories\EditorRequestFactory;
|
use Tests\RequestFactories\EditorRequestFactory;
|
||||||
|
@ -52,6 +53,7 @@ class FormRequest extends RequestFactory
|
||||||
'export' => ExportData::from([])->toArray(),
|
'export' => ExportData::from([])->toArray(),
|
||||||
'needs_prevention' => $this->faker->boolean(),
|
'needs_prevention' => $this->faker->boolean(),
|
||||||
'prevention_text' => EditorRequestFactory::new()->create(),
|
'prevention_text' => EditorRequestFactory::new()->create(),
|
||||||
|
'prevention_conditions' => Condition::defaults(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace Tests\Feature\Form;
|
||||||
use App\Fileshare\Data\FileshareResourceData;
|
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 App\Lib\Editor\Condition;
|
||||||
use App\Lib\Editor\EditorData;
|
use App\Lib\Editor\EditorData;
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
use Tests\RequestFactories\EditorRequestFactory;
|
use Tests\RequestFactories\EditorRequestFactory;
|
||||||
|
@ -132,11 +133,12 @@ class FormUpdateActionTest extends FormTestCase
|
||||||
$form = Form::factory()->create();
|
$form = Form::factory()->create();
|
||||||
$payload = FormRequest::new()
|
$payload = FormRequest::new()
|
||||||
->preventionText(EditorRequestFactory::new()->text(10, 'lorem ipsum'))
|
->preventionText(EditorRequestFactory::new()->text(10, 'lorem ipsum'))
|
||||||
->state(['needs_prevention' => true])
|
->state(['needs_prevention' => true, 'prevention_conditions' => ['mode' => 'all', 'ifs' => [['field' => 'vorname', 'value' => 'Max', 'comparator' => 'isEqual']]]])
|
||||||
->create();
|
->create();
|
||||||
|
|
||||||
$this->patchJson(route('form.update', ['form' => $form]), $payload);
|
$this->patchJson(route('form.update', ['form' => $form]), $payload);
|
||||||
$this->assertTrue($form->fresh()->needs_prevention);
|
$this->assertTrue($form->fresh()->needs_prevention);
|
||||||
$this->assertEquals('lorem ipsum', $form->fresh()->prevention_text->blocks[0]['data']['text']);
|
$this->assertEquals('lorem ipsum', $form->fresh()->prevention_text->blocks[0]['data']['text']);
|
||||||
|
$this->assertEquals(['mode' => 'all', 'ifs' => [['field' => 'vorname', 'value' => 'Max', 'comparator' => 'isEqual']]], $form->fresh()->prevention_conditions->toArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use App\Form\Enums\SpecialType;
|
||||||
use App\Form\Models\Form;
|
use App\Form\Models\Form;
|
||||||
use App\Form\Models\Participant;
|
use App\Form\Models\Participant;
|
||||||
use App\Invoice\InvoiceSettings;
|
use App\Invoice\InvoiceSettings;
|
||||||
|
use App\Lib\Editor\Condition;
|
||||||
use App\Prevention\Mails\PreventionRememberMail;
|
use App\Prevention\Mails\PreventionRememberMail;
|
||||||
use App\Member\Member;
|
use App\Member\Member;
|
||||||
use App\Member\Membership;
|
use App\Member\Membership;
|
||||||
|
@ -37,6 +38,19 @@ class PreventionTest extends TestCase
|
||||||
$this->assertEquals(now()->format('Y-m-d'), $participant->fresh()->last_remembered_at->format('Y-m-d'));
|
$this->assertEquals(now()->format('Y-m-d'), $participant->fresh()->last_remembered_at->format('Y-m-d'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testItDoesntRememberWhenConditionDoesntMatch(): void
|
||||||
|
{
|
||||||
|
Mail::fake();
|
||||||
|
$form = $this->createForm();
|
||||||
|
$form->update(['prevention_conditions' => Condition::from(['mode' => 'all', 'ifs' => [['field' => 'vorname', 'comparator' => 'isEqual', 'value' => 'Max']]])]);
|
||||||
|
$participant = $this->createParticipant($form);
|
||||||
|
$participant->update(['data' => [...$participant->data, 'vorname' => 'Jane']]);
|
||||||
|
|
||||||
|
PreventionRememberAction::run();
|
||||||
|
|
||||||
|
$this->assertNull($participant->fresh()->last_remembered_at);
|
||||||
|
}
|
||||||
|
|
||||||
public function testItRemembersWhenRememberIsDue(): void
|
public function testItRemembersWhenRememberIsDue(): void
|
||||||
{
|
{
|
||||||
Mail::fake();
|
Mail::fake();
|
||||||
|
@ -82,7 +96,7 @@ class PreventionTest extends TestCase
|
||||||
$this->assertNull($participant->fresh()->last_remembered_at);
|
$this->assertNull($participant->fresh()->last_remembered_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItDoesntRememberWhenMemberIsNotALeader(): void
|
public function testItRemembersNonLeaders(): void
|
||||||
{
|
{
|
||||||
Mail::fake();
|
Mail::fake();
|
||||||
$form = $this->createForm();
|
$form = $this->createForm();
|
||||||
|
@ -91,7 +105,7 @@ class PreventionTest extends TestCase
|
||||||
|
|
||||||
PreventionRememberAction::run();
|
PreventionRememberAction::run();
|
||||||
|
|
||||||
$this->assertNull($participant->fresh()->last_remembered_at);
|
$this->assertNotNull($participant->fresh()->last_remembered_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function attributes(): Generator
|
protected function attributes(): Generator
|
||||||
|
|
Loading…
Reference in New Issue