Compare commits
	
		
			6 Commits
		
	
	
		
			fc2b3d6885
			...
			94f91cbda7
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 94f91cbda7 | |
|  | 5c577acdbf | |
|  | b6bb7d4113 | |
|  | e03c3a7ed3 | |
|  | 65a6614831 | |
|  | 7c656afce8 | 
|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| namespace App\Member; | ||||
| 
 | ||||
| use stdClass; | ||||
| use App\Confession; | ||||
| use App\Country; | ||||
| use App\Course\Models\CourseMember; | ||||
|  | @ -13,6 +14,7 @@ use App\Nami\HasNamiField; | |||
| use App\Nationality; | ||||
| use App\Payment\Subscription; | ||||
| use App\Pdf\Sender; | ||||
| use App\Prevention\Contracts\YearlyPreventable; | ||||
| use App\Region; | ||||
| use App\Setting\NamiSettings; | ||||
| use Carbon\Carbon; | ||||
|  | @ -40,7 +42,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne; | |||
|  * @property string $subscription_name | ||||
|  * @property int    $pending_payment | ||||
|  */ | ||||
| class Member extends Model implements Geolocatable | ||||
| class Member extends Model implements Geolocatable, YearlyPreventable | ||||
| { | ||||
|     use Notifiable; | ||||
|     use HasNamiField; | ||||
|  | @ -191,7 +193,7 @@ class Member extends Model implements Geolocatable | |||
| 
 | ||||
|     protected function getAusstand(): int | ||||
|     { | ||||
|         return (int) $this->invoicePositions()->whereHas('invoice', fn ($query) => $query->whereNeedsPayment())->sum('price'); | ||||
|         return (int) $this->invoicePositions()->whereHas('invoice', fn($query) => $query->whereNeedsPayment())->sum('price'); | ||||
|     } | ||||
| 
 | ||||
|     // ---------------------------------- Relations ----------------------------------
 | ||||
|  | @ -339,7 +341,7 @@ class Member extends Model implements Geolocatable | |||
|         return $query->addSelect([ | ||||
|             'pending_payment' => InvoicePosition::selectRaw('SUM(price)') | ||||
|                 ->whereColumn('invoice_positions.member_id', 'members.id') | ||||
|                 ->whereHas('invoice', fn ($query) => $query->whereNeedsPayment()), | ||||
|                 ->whereHas('invoice', fn($query) => $query->whereNeedsPayment()), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|  | @ -350,7 +352,7 @@ class Member extends Model implements Geolocatable | |||
|      */ | ||||
|     public function scopeWhereHasPendingPayment(Builder $query): Builder | ||||
|     { | ||||
|         return $query->whereHas('invoicePositions', fn ($q) => $q->whereHas('invoice', fn ($q) => $q->whereNeedsPayment())); | ||||
|         return $query->whereHas('invoicePositions', fn($q) => $q->whereHas('invoice', fn($q) => $q->whereNeedsPayment())); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -499,7 +501,7 @@ class Member extends Model implements Geolocatable | |||
|             'name' => $this->fullname, | ||||
|             'address' => $this->address, | ||||
|             'zipLocation' => $this->zip . ' ' . $this->location, | ||||
|             'mglnr' => Lazy::create(fn () => 'Mglnr.: ' . $this->nami_id), | ||||
|             'mglnr' => Lazy::create(fn() => 'Mglnr.: ' . $this->nami_id), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|  | @ -508,7 +510,7 @@ class Member extends Model implements Geolocatable | |||
|      */ | ||||
|     public static function forSelect(): array | ||||
|     { | ||||
|         return static::select(['id', 'firstname', 'lastname'])->get()->map(fn ($member) => ['id' => $member->id, 'name' => $member->fullname])->toArray(); | ||||
|         return static::select(['id', 'firstname', 'lastname'])->get()->map(fn($member) => ['id' => $member->id, 'name' => $member->fullname])->toArray(); | ||||
|     } | ||||
| 
 | ||||
|     // -------------------------------- Geolocation --------------------------------
 | ||||
|  | @ -567,7 +569,24 @@ class Member extends Model implements Geolocatable | |||
|             'age_group_icon' => $this->ageGroupMemberships->first()?->subactivity->slug, | ||||
|             'is_leader' => $this->leaderMemberships()->count() > 0, | ||||
|             'memberships' => $this->memberships()->active()->get() | ||||
|                 ->map(fn ($membership) => [...$membership->only('activity_id', 'subactivity_id'), 'both' => $membership->activity_id . '|' . $membership->subactivity_id, 'with_group' => $membership->group_id . '|' . $membership->activity_id . '|' . $membership->subactivity_id]), | ||||
|                 ->map(fn($membership) => [...$membership->only('activity_id', 'subactivity_id'), 'both' => $membership->activity_id . '|' . $membership->subactivity_id, 'with_group' => $membership->group_id . '|' . $membership->activity_id . '|' . $membership->subactivity_id]), | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     // -------------------------------- Prevention ---------------------------------
 | ||||
|     // *****************************************************************************
 | ||||
|     public function preventableSubject(): string | ||||
|     { | ||||
|         return 'Nachweise erforderlich'; | ||||
|     } | ||||
| 
 | ||||
|     public function preventableLayout(): string | ||||
|     { | ||||
|         return 'mail.prevention.prevention-remember-participant'; | ||||
|     } | ||||
| 
 | ||||
|     public function getMailRecipient(): stdClass | ||||
|     { | ||||
|         return (object) ['name' => $this->fullname, 'email' => $this->email]; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ class SettingStoreAction | |||
|     { | ||||
|         return [ | ||||
|             'formmail' => 'array', | ||||
|             'yearlymail' => 'array', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|  | @ -26,6 +27,7 @@ class SettingStoreAction | |||
|     { | ||||
|         $settings = app(PreventionSettings::class); | ||||
|         $settings->formmail = EditorData::from($request->formmail); | ||||
|         $settings->yearlymail = EditorData::from($request->yearlymail); | ||||
|         $settings->save(); | ||||
| 
 | ||||
|         Succeeded::message('Einstellungen gespeichert.')->dispatch(); | ||||
|  |  | |||
|  | @ -0,0 +1,20 @@ | |||
| <?php | ||||
| 
 | ||||
| namespace App\Prevention\Contracts; | ||||
| 
 | ||||
| use App\Prevention\Enums\Prevention; | ||||
| use stdClass; | ||||
| 
 | ||||
| interface YearlyPreventable | ||||
| { | ||||
| 
 | ||||
|     public function preventableLayout(): string; | ||||
|     public function preventableSubject(): string; | ||||
| 
 | ||||
|     /** | ||||
|      * @return array<int, Prevention> | ||||
|      */ | ||||
|     public function preventions(): array; | ||||
| 
 | ||||
|     public function getMailRecipient(): stdClass; | ||||
| } | ||||
|  | @ -0,0 +1,65 @@ | |||
| <?php | ||||
| 
 | ||||
| namespace App\Prevention\Mails; | ||||
| 
 | ||||
| use App\Invoice\InvoiceSettings; | ||||
| use App\Lib\Editor\EditorData; | ||||
| use App\Prevention\Contracts\Preventable; | ||||
| use App\Prevention\Contracts\YearlyPreventable; | ||||
| 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 YearlyMail extends Mailable | ||||
| { | ||||
|     use Queueable, SerializesModels; | ||||
| 
 | ||||
|     public InvoiceSettings $settings; | ||||
| 
 | ||||
|     /** | ||||
|      * Create a new message instance. | ||||
|      */ | ||||
|     public function __construct(public YearlyPreventable $preventable, public EditorData $bodyText) | ||||
|     { | ||||
|         $this->settings = app(InvoiceSettings::class); | ||||
|         $this->bodyText = $this->bodyText | ||||
|             ->replaceWithList('wanted', collect($preventable->preventions())->map(fn($prevention) => $prevention->text())->toArray()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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 []; | ||||
|     } | ||||
| } | ||||
|  | @ -9,6 +9,7 @@ class PreventionSettings extends LocalSettings | |||
| { | ||||
| 
 | ||||
|     public EditorData $formmail; | ||||
|     public EditorData $yearlymail; | ||||
| 
 | ||||
|     public static function group(): string | ||||
|     { | ||||
|  |  | |||
|  | @ -0,0 +1,11 @@ | |||
| <?php | ||||
| 
 | ||||
| use Spatie\LaravelSettings\Migrations\SettingsMigration; | ||||
| 
 | ||||
| return new class extends SettingsMigration | ||||
| { | ||||
|     public function up(): void | ||||
|     { | ||||
|         $this->migrator->add('prevention.yearlymail', ['time' => 1, 'blocks' => [], 'version' => '1.0']); | ||||
|     } | ||||
| }; | ||||
|  | @ -91,6 +91,8 @@ services: | |||
|             - ./data/db:/var/lib/mysql | ||||
| 
 | ||||
|     socketi: | ||||
|         ports: | ||||
|             - '6001:6001' | ||||
|         image: quay.io/soketi/soketi:89604f268623cf799573178a7ba56b7491416bde-16-debian | ||||
|         environment: | ||||
|             SOKETI_DEFAULT_APP_ID: adremaid | ||||
|  | @ -103,6 +105,8 @@ services: | |||
|             - ./data/redis:/data | ||||
| 
 | ||||
|     meilisearch: | ||||
|         ports: | ||||
|             - '7700:7700' | ||||
|         image: getmeili/meilisearch:v1.6 | ||||
|         volumes: | ||||
|             - ./data/meilisearch:/meili_data | ||||
|  |  | |||
|  | @ -1,47 +1,19 @@ | |||
| <template> | ||||
|     <ui-popup v-if="visible === true" heading="Filtern" @close="visible = false"> | ||||
|         <div class="grid gap-3 md:grid-cols-2"> | ||||
|             <slot name="fields"></slot> | ||||
|         </div> | ||||
|     </ui-popup> | ||||
|     <div class="px-6 py-2 border-b border-gray-600" :class="visibleDesktopBlock"> | ||||
|         <div class="flex items-end space-x-3"> | ||||
|             <slot name="buttons"></slot> | ||||
|             <ui-icon-button v-if="filterable" icon="filter" @click="filterVisible = !filterVisible">Filtern</ui-icon-button> | ||||
|         </div> | ||||
|         <ui-box v-if="filterVisible" class="mt-3"> | ||||
|             <div class="grid grid-cols-4 gap-3 items-end"> | ||||
|                 <slot name="fields"></slot> | ||||
|                 <ui-icon-button class="col-start-1" icon="close" @click="filterVisible = false">Schließen</ui-icon-button> | ||||
|             </div> | ||||
|         </ui-box> | ||||
|     </div> | ||||
|     <div class="px-6 py-2 border-b border-gray-600 items-center space-x-3" :class="visibleMobile"> | ||||
|     <page-filter-sidebar v-if="visible === true" @close="visible = false"> | ||||
|         <slot name="fields"></slot> | ||||
|     </page-filter-sidebar> | ||||
|     <div class="px-6 py-2 border-b border-gray-600 items-center space-x-3"> | ||||
|         <div class="flex flex-col sm:flex-row items-stretch sm:items-end space-y-1 sm:space-y-0 sm:space-x-3"> | ||||
|             <slot name="buttons"></slot> | ||||
|             <ui-icon-button v-if="filterable" icon="filter" @click="visible = true">Filtern</ui-icon-button> | ||||
|             <ui-icon-button v-if="!!$slots.fields" icon="filter" @click="visible = true">Filtern</ui-icon-button> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import {defineProps, ref} from 'vue'; | ||||
| import useBreakpoints from '../../composables/useBreakpoints.js'; | ||||
| import {ref} from 'vue'; | ||||
| 
 | ||||
| defineEmits(['close']); | ||||
| 
 | ||||
| const visible = ref(false); | ||||
| 
 | ||||
| const filterVisible = ref(false); | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|     breakpoint: { | ||||
|         type: String, | ||||
|         required: true, | ||||
|     }, | ||||
|     filterable: { | ||||
|         type: Boolean, | ||||
|         default: () => true, | ||||
|     }, | ||||
| }); | ||||
| 
 | ||||
| const {visibleDesktopBlock, visibleMobile} = useBreakpoints(props); | ||||
| </script> | ||||
|  |  | |||
|  | @ -0,0 +1,16 @@ | |||
| <template> | ||||
|     <ui-sidebar :max="0" @close="$emit('close')"> | ||||
|         <page-header title="Filtern" @close="$emit('close')"></page-header> | ||||
|         <div class="grid gap-6 p-6"> | ||||
|             <slot></slot> | ||||
|         </div> | ||||
|     </ui-sidebar> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import {ref} from 'vue'; | ||||
| 
 | ||||
| const visible = ref(false); | ||||
| 
 | ||||
| defineEmits(['close']); | ||||
| </script> | ||||
|  | @ -1,6 +1,5 @@ | |||
| <template> | ||||
|     <div | ||||
|         class="fixed w-full w-[80vw] max-w-[40rem] shadow-2xl bg-gray-600 right-0 top-0 h-full flex flex-col group is-bright"> | ||||
|     <div class="fixed shadow-2xl bg-gray-600 right-0 top-0 h-full flex flex-col group is-bright" :class="widths[max]"> | ||||
|         <suspense> | ||||
|             <slot></slot> | ||||
| 
 | ||||
|  | @ -18,4 +17,20 @@ | |||
| 
 | ||||
| <script setup> | ||||
| defineEmits(['close']); | ||||
| 
 | ||||
| const widths = { | ||||
|     40: 'w-full w-[80vw] max-w-[40rem]', | ||||
|     30: 'w-full w-[80vw] max-w-[30rem]', | ||||
|     20: 'w-full w-[80vw] max-w-[20rem]', | ||||
|     10: 'w-full w-[80vw] max-w-[10rem]', | ||||
|     0: '', | ||||
| }; | ||||
| 
 | ||||
| defineProps({ | ||||
|     max: { | ||||
|         default: () => 40, | ||||
|         type: Number, | ||||
|         required: false, | ||||
|     }, | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,9 +1,15 @@ | |||
| <template> | ||||
|     <div class="flex-none w-maxc flex flex-col justify-between border-b-2 group-[.is-popup]:border-zinc-500 mb-3"> | ||||
|     <div class="flex-none w-maxc flex flex-col justify-between border-b-2 border-gray-500 group-[.is-popup]:border-zinc-500 mb-3"> | ||||
|         <div class="flex space-x-1 px-2"> | ||||
|             <a v-for="(item, index) in entries" :key="index" href="#" class="rounded-t-lg py-1 px-3 text-zinc-300" | ||||
|                 :class="index === modelValue ? `group-[.is-popup]:bg-zinc-600` : ''" @click.prevent="openMenu(index)" | ||||
|                 v-text="item.title"></a> | ||||
|             <a | ||||
|                 v-for="(item, index) in entries" | ||||
|                 :key="index" | ||||
|                 href="#" | ||||
|                 class="rounded-t-lg py-1 px-3 text-zinc-300" | ||||
|                 :class="index === modelValue ? `bg-gray-700 group-[.is-popup]:bg-zinc-600` : ''" | ||||
|                 @click.prevent="openMenu(index)" | ||||
|                 v-text="item.title" | ||||
|             ></a> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  |  | |||
|  | @ -1,46 +0,0 @@ | |||
| import {computed} from 'vue'; | ||||
| 
 | ||||
| export default function (props) { | ||||
|     const visibleMobile = computed(() => { | ||||
|         return { | ||||
|             sm: 'flex sm:hidden', | ||||
|             md: 'flex md:hidden', | ||||
|             lg: 'flex lg:hidden', | ||||
|             xl: 'flex xl:hidden', | ||||
|         }[props.breakpoint]; | ||||
|     }); | ||||
| 
 | ||||
|     const visibleDesktop = computed(() => { | ||||
|         return { | ||||
|             sm: 'hidden sm:flex', | ||||
|             md: 'hidden md:flex', | ||||
|             lg: 'hidden lg:flex', | ||||
|             xl: 'hidden xl:flex', | ||||
|         }[props.breakpoint]; | ||||
|     }); | ||||
| 
 | ||||
|     const visibleMobileBlock = computed(() => { | ||||
|         return { | ||||
|             sm: 'block sm:hidden', | ||||
|             md: 'block md:hidden', | ||||
|             lg: 'block lg:hidden', | ||||
|             xl: 'block xl:hidden', | ||||
|         }[props.breakpoint]; | ||||
|     }); | ||||
| 
 | ||||
|     const visibleDesktopBlock = computed(() => { | ||||
|         return { | ||||
|             sm: 'hidden sm:block', | ||||
|             md: 'hidden md:block', | ||||
|             lg: 'hidden lg:block', | ||||
|             xl: 'hidden xl:block', | ||||
|         }[props.breakpoint]; | ||||
|     }); | ||||
| 
 | ||||
|     return { | ||||
|         visibleMobile, | ||||
|         visibleDesktop, | ||||
|         visibleDesktopBlock, | ||||
|         visibleMobileBlock, | ||||
|     }; | ||||
| } | ||||
|  | @ -144,7 +144,7 @@ | |||
|             <conditions-form id="filesettings" :single="single" :value="fileSettingPopup.properties.conditions" @save="saveFileConditions"> </conditions-form> | ||||
|         </ui-popup> | ||||
| 
 | ||||
|         <page-filter breakpoint="xl" :filterable="false"> | ||||
|         <page-filter> | ||||
|             <template #buttons> | ||||
|                 <f-text id="search" :model-value="getFilter('search')" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text> | ||||
|                 <f-switch id="past" :model-value="getFilter('past')" label="vergangene zeigen" name="past" size="sm" @update:model-value="setFilter('past', $event)"></f-switch> | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ | |||
|                 </div> | ||||
|             </div> | ||||
|         </ui-popup> | ||||
|         <page-filter breakpoint="lg"> | ||||
|         <page-filter> | ||||
|             <template #buttons> | ||||
|                 <f-text id="search" v-model="innerFilter.search" name="search" label="Suchen" size="sm"></f-text> | ||||
|                 <ui-icon-button icon="plus" @click="editing = {participant: null, preview: JSON.stringify(meta.form_config)}">Hinzufügen</ui-icon-button> | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ | |||
|                 </section> | ||||
|             </form> | ||||
|         </ui-popup> | ||||
|         <page-filter breakpoint="xl" :filterable="false"> | ||||
|         <page-filter> | ||||
|             <template #buttons> | ||||
|                 <f-text id="search" :model-value="getFilter('search')" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text> | ||||
|                 <f-multipleselect | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| <template></template> | ||||
| 
 | ||||
| <script setup></script> | ||||
|  | @ -37,53 +37,14 @@ | |||
|                 <span class="hidden xl:inline">Anwenden</span> | ||||
|             </button> | ||||
|         </ui-popup> | ||||
|         <page-filter breakpoint="xl"> | ||||
| 
 | ||||
|         <page-filter> | ||||
|             <template #fields> | ||||
|                 <f-switch | ||||
|                     v-show="hasModule('bill')" | ||||
|                     id="ausstand" | ||||
|                     name="ausstand" | ||||
|                     :model-value="getFilter('ausstand')" | ||||
|                     label="Nur Ausstände" | ||||
|                     size="sm" | ||||
|                     @update:model-value="setFilter('ausstand', $event)" | ||||
|                 ></f-switch> | ||||
|                 <f-select | ||||
|                     id="has_vk" | ||||
|                     name="has_vk" | ||||
|                     :model-value="getFilter('has_vk')" | ||||
|                     label="Verhaltenskodex unterschrieben" | ||||
|                     size="sm" | ||||
|                     :options="meta.boolean_filter" | ||||
|                     @update:model-value="setFilter('has_vk', $event)" | ||||
|                 ></f-select> | ||||
|                 <f-select | ||||
|                     id="has_svk" | ||||
|                     name="has_svk" | ||||
|                     :model-value="getFilter('has_svk')" | ||||
|                     label="SVK unterschrieben" | ||||
|                     size="sm" | ||||
|                     :options="meta.boolean_filter" | ||||
|                     @update:model-value="setFilter('has_svk', $event)" | ||||
|                 ></f-select> | ||||
|                 <f-multipleselect | ||||
|                     id="group_ids" | ||||
|                     :options="meta.groups" | ||||
|                     :model-value="getFilter('group_ids')" | ||||
|                     label="Gruppierungen" | ||||
|                     size="sm" | ||||
|                     @update:model-value="setFilter('group_ids', $event)" | ||||
|                 ></f-multipleselect> | ||||
|                 <f-select | ||||
|                     v-show="hasModule('bill')" | ||||
|                     id="billKinds" | ||||
|                     name="billKinds" | ||||
|                     :options="meta.billKinds" | ||||
|                     :model-value="getFilter('bill_kind')" | ||||
|                     label="Rechnung" | ||||
|                     size="sm" | ||||
|                     @update:model-value="setFilter('bill_kind', $event)" | ||||
|                 ></f-select> | ||||
|                 <f-switch v-show="hasModule('bill')" id="ausstand" name="ausstand" v-model="filter.ausstand" label="Nur Ausstände" size="sm"></f-switch> | ||||
|                 <f-select id="has_vk" name="has_vk" v-model="filter.has_vk" label="Verhaltenskodex unterschrieben" size="sm" :options="meta.boolean_filter"></f-select> | ||||
|                 <f-select id="has_svk" name="has_svk" v-model="filter.has_svk" label="SVK unterschrieben" size="sm" :options="meta.boolean_filter"></f-select> | ||||
|                 <f-multipleselect id="group_ids" :options="meta.groups" v-model="filter.group_ids" label="Gruppierungen" size="sm"></f-multipleselect> | ||||
|                 <f-select v-show="hasModule('bill')" id="billKinds" name="billKinds" :options="meta.billKinds" v-model="filter.bill_kind" label="Rechnung" size="sm"></f-select> | ||||
|                 <button class="btn btn-primary label mr-2" @click.prevent="membershipFilters = getFilter('memberships')"> | ||||
|                     <ui-sprite class="w-3 h-3 xl:mr-2" src="filter"></ui-sprite> | ||||
|                     <span class="hidden xl:inline">Mitgliedschaften</span> | ||||
|  | @ -174,6 +135,11 @@ const single = ref(null); | |||
| const deleting = ref(null); | ||||
| const membershipFilters = ref(null); | ||||
| 
 | ||||
| var filter = ref({ | ||||
|     ausstand: false, | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
| const props = defineProps(indexProps); | ||||
| var { router, data, meta, getFilter, setFilter, filterString, reloadPage } = useIndex(props.data, 'member'); | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,24 +3,31 @@ | |||
|         <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> | ||||
|                 <ui-tabs v-model="active" class="mt-2" :entries="tabs"></ui-tabs> | ||||
|                 <f-editor v-if="active === 0" id="formmail" v-model="data.formmail" label="E-Mail für Veranstaltungs-TN"></f-editor> | ||||
|                 <f-editor v-if="active === 1" id="yearlymail" v-model="data.yearlymail" label="Jährliche Präventions-Erinnerung"></f-editor> | ||||
|             </form> | ||||
|         </setting-layout> | ||||
|     </page-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="js" setup> | ||||
| import { ref } from 'vue'; | ||||
| import { reactive, ref } from 'vue'; | ||||
| import { useApiIndex } from '../../composables/useApiIndex.js'; | ||||
| import SettingLayout from '../setting/Layout.vue'; | ||||
| 
 | ||||
| const tabs = [ | ||||
|     { title: 'für Veranstaltungen' }, | ||||
|     { title: 'Jährlich' }, | ||||
| ]; | ||||
| const active = ref(0); | ||||
| 
 | ||||
| const { axios, data, reload } = useApiIndex('/api/prevention', 'prevention'); | ||||
| const loaded = ref(false); | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ use App\Lib\Editor\Condition; | |||
| use App\Prevention\Mails\PreventionRememberMail; | ||||
| use App\Member\Member; | ||||
| use App\Member\Membership; | ||||
| use App\Prevention\Mails\YearlyMail; | ||||
| use App\Prevention\PreventionSettings; | ||||
| use Generator; | ||||
| use Illuminate\Foundation\Testing\DatabaseTransactions; | ||||
|  | @ -22,285 +23,272 @@ use Tests\Lib\CreatesFormFields; | |||
| use Tests\RequestFactories\EditorRequestFactory; | ||||
| use Tests\TestCase; | ||||
| 
 | ||||
| class PreventionTest extends TestCase | ||||
| uses(DatabaseTransactions::class); | ||||
| uses(CreatesFormFields::class); | ||||
| 
 | ||||
| function createForm(): Form | ||||
| { | ||||
| 
 | ||||
|     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 testItDoesntRememberPastEvents(): void | ||||
|     { | ||||
|         Mail::fake(); | ||||
|         $form = $this->createForm(); | ||||
|         $participant = $this->createParticipant($form); | ||||
|         $form->update(['from' => now()->subDay()]); | ||||
| 
 | ||||
|         PreventionRememberAction::run(); | ||||
| 
 | ||||
|         $this->assertNull($participant->fresh()->last_remembered_at); | ||||
|     } | ||||
| 
 | ||||
|     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 | ||||
|     { | ||||
|         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 testItRemembersNonLeaders(): void | ||||
|     { | ||||
|         Mail::fake(); | ||||
|         $form = $this->createForm(); | ||||
|         $participant = $this->createParticipant($form); | ||||
|         $participant->member->memberships->each->delete(); | ||||
| 
 | ||||
|         PreventionRememberAction::run(); | ||||
| 
 | ||||
|         $this->assertNotNull($participant->fresh()->last_remembered_at); | ||||
|     } | ||||
| 
 | ||||
|     public static function attributes(): Generator | ||||
|     { | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => null, 'ps_at' => now()], | ||||
|             'preventions' => [Prevention::EFZ] | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => now(), 'ps_at' => null], | ||||
|             'preventions' => [Prevention::PS] | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => now()->subDay(), 'ps_at' => now()], | ||||
|             'preventions' => [] | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subDay()], | ||||
|             'preventions' => [] | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => now()->subYears(5)->subDay(), 'ps_at' => now()], | ||||
|             'preventions' => [Prevention::EFZ] | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => now(), 'ps_at' => null], | ||||
|             'preventions' => [Prevention::PS] | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subYears(5)->subDay()], | ||||
|             'preventions' => [Prevention::MOREPS] | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subYears(5)->subDay(), 'more_ps_at' => now()], | ||||
|             'preventions' => [] | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subYears(15), 'more_ps_at' => now()->subYears(5)->subDay()], | ||||
|             'preventions' => [Prevention::MOREPS], | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => false, 'efz' => now(), 'ps_at' => now()], | ||||
|             'preventions' => [Prevention::VK], | ||||
|         ]; | ||||
| 
 | ||||
|         yield [ | ||||
|             'attrs' => ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subYears(7)], | ||||
|             'preventions' => [Prevention::MOREPS], | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array<int, Prevention> $preventions | ||||
|      * @param array<string, mixed> $memberAttributes | ||||
|      */ | ||||
|     #[DataProvider('attributes')]
 | ||||
|     public function testItRemembersMember(array $attrs, array $preventions): void | ||||
|     { | ||||
|         Mail::fake(); | ||||
|         $form = $this->createForm(); | ||||
|         $participant = $this->createParticipant($form); | ||||
|         $participant->member->update($attrs); | ||||
| 
 | ||||
|         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 testItDoesntRememberParticipantThatHasNoMail(): void | ||||
|     { | ||||
|         Mail::fake(); | ||||
|         $form = $this->createForm(); | ||||
|         $participant = $this->createParticipant($form); | ||||
|         $participant->update(['data' => [...$participant->data, 'email' => '']]); | ||||
| 
 | ||||
|         PreventionRememberAction::run(); | ||||
| 
 | ||||
|         Mail::assertNotSent(PreventionRememberMail::class); | ||||
|     } | ||||
| 
 | ||||
|     public function testItRendersMail(): void | ||||
|     { | ||||
|         InvoiceSettings::fake(['from_long' => 'Stamm Beispiel']); | ||||
|         $form = $this->createForm(); | ||||
|         $participant = $this->createParticipant($form); | ||||
|         (new PreventionRememberMail($participant, app(PreventionSettings::class)->formmail)) | ||||
|             ->assertSeeInText($participant->member->firstname) | ||||
|             ->assertSeeInText($participant->member->lastname) | ||||
|             ->assertSeeInText('Stamm Beispiel'); | ||||
|     } | ||||
| 
 | ||||
|     public function testItRendersSetttingMail(): void | ||||
|     { | ||||
|         Mail::fake(); | ||||
|         app(PreventionSettings::class)->fill([ | ||||
|             'formmail' => EditorRequestFactory::new()->paragraphs(["lorem lala {formname} g", "{wanted}", "bbb"])->toData() | ||||
|         ])->save(); | ||||
|         $form = $this->createForm(); | ||||
|         $this->createParticipant($form); | ||||
| 
 | ||||
|         PreventionRememberAction::run(); | ||||
| 
 | ||||
|         Mail::assertSent(PreventionRememberMail::class, fn ($mail) => $mail->bodyText->hasAll([ | ||||
|             'lorem lala ' . $form->name, | ||||
|             'erweitertes' | ||||
|         ])); | ||||
|     } | ||||
| 
 | ||||
|     public function testItAppendsTextOfForm(): void | ||||
|     { | ||||
|         Mail::fake(); | ||||
|         app(PreventionSettings::class)->fill([ | ||||
|             'formmail' => EditorRequestFactory::new()->paragraphs(["::first::"])->toData() | ||||
|         ])->save(); | ||||
|         $form = $this->createForm(); | ||||
|         $form->update(['prevention_text' => EditorRequestFactory::new()->paragraphs(['event'])->toData()]); | ||||
|         $this->createParticipant($form); | ||||
| 
 | ||||
|         PreventionRememberAction::run(); | ||||
| 
 | ||||
|         Mail::assertSent(PreventionRememberMail::class, fn ($mail) => $mail->bodyText->hasAll([ | ||||
|             'event' | ||||
|         ])); | ||||
|     } | ||||
| 
 | ||||
|     public function testItDoesntAppendTextTwice(): void | ||||
|     { | ||||
|         Mail::fake(); | ||||
|         app(PreventionSettings::class)->fill(['frommail' => EditorRequestFactory::new()->paragraphs(["::first::"])->toData()])->save(); | ||||
|         tap($this->createForm(), function ($f) { | ||||
|             $f->update(['prevention_text' => EditorRequestFactory::new()->paragraphs(['oberhausen'])->toData()]); | ||||
|             $this->createParticipant($f); | ||||
|         }); | ||||
|         tap($this->createForm(), function ($f) { | ||||
|             $f->update(['prevention_text' => EditorRequestFactory::new()->paragraphs(['siegburg'])->toData()]); | ||||
|             $this->createParticipant($f); | ||||
|         }); | ||||
| 
 | ||||
|         PreventionRememberAction::run(); | ||||
| 
 | ||||
|         Mail::assertSent(PreventionRememberMail::class, fn ($mail) => $mail->bodyText->hasAll(['oberhausen']) && !$mail->bodyText->hasAll(['siegburg'])); | ||||
|     } | ||||
| 
 | ||||
|     public function testItDisplaysBodyTextInMail(): void | ||||
|     { | ||||
|         $form = $this->createForm(); | ||||
|         $participant = $this->createParticipant($form); | ||||
| 
 | ||||
|         $mail = new PreventionRememberMail($participant, EditorRequestFactory::new()->paragraphs(['ggtt'])->toData()); | ||||
|         $mail->assertSeeInText('ggtt'); | ||||
|     } | ||||
| 
 | ||||
|     protected function createForm(): Form | ||||
|     { | ||||
|         return Form::factory()->fields([ | ||||
|             $this->textField('vorname')->namiType(NamiType::FIRSTNAME)->specialType(SpecialType::FIRSTNAME), | ||||
|             $this->textField('nachname')->namiType(NamiType::FIRSTNAME)->specialType(SpecialType::LASTNAME), | ||||
|             $this->textField('email')->namiType(NamiType::FIRSTNAME)->specialType(SpecialType::EMAIL), | ||||
|         ])->create(['needs_prevention' => true]); | ||||
|     } | ||||
| 
 | ||||
|     protected function createParticipant(Form $form): Participant | ||||
|     { | ||||
|         return Participant::factory()->for($form)->data([ | ||||
|             'vorname' => 'Max', | ||||
|             'nachname' => 'Muster', | ||||
|             'email' => 'mail@a.de', | ||||
|         ])->for(Member::factory()->defaults()->has(Membership::factory()->inLocal('€ LeiterIn', 'Wölfling')))->create(); | ||||
|     } | ||||
|     return Form::factory()->fields([ | ||||
|         test()->textField('vorname')->namiType(NamiType::FIRSTNAME)->specialType(SpecialType::FIRSTNAME), | ||||
|         test()->textField('nachname')->namiType(NamiType::FIRSTNAME)->specialType(SpecialType::LASTNAME), | ||||
|         test()->textField('email')->namiType(NamiType::FIRSTNAME)->specialType(SpecialType::EMAIL), | ||||
|     ])->create(['needs_prevention' => true]); | ||||
| } | ||||
| 
 | ||||
| function createParticipant(Form $form): Participant | ||||
| { | ||||
|     return Participant::factory()->for($form)->data([ | ||||
|         'vorname' => 'Max', | ||||
|         'nachname' => 'Muster', | ||||
|         'email' => 'mail@a.de', | ||||
|     ])->for(Member::factory()->defaults()->has(Membership::factory()->inLocal('€ LeiterIn', 'Wölfling')))->create(); | ||||
| } | ||||
| 
 | ||||
| function createMember(array $attributes): Member | ||||
| { | ||||
|     return Member::factory()->defaults()->has(Membership::factory()->inLocal('€ LeiterIn', 'Wölfling'))->create($attributes); | ||||
| } | ||||
| 
 | ||||
| dataset('attributes', fn() => [ | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => null, 'ps_at' => now()], | ||||
|         [Prevention::EFZ] | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => now(), 'ps_at' => null], | ||||
|         [Prevention::PS] | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => now()->subDay(), 'ps_at' => now()], | ||||
|         [] | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subDay()], | ||||
|         [] | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => now()->subYears(5)->subDay(), 'ps_at' => now()], | ||||
|         [Prevention::EFZ] | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => now(), 'ps_at' => null], | ||||
|         [Prevention::PS] | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subYears(5)->subDay()], | ||||
|         [Prevention::MOREPS] | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subYears(5)->subDay(), 'more_ps_at' => now()], | ||||
|         [] | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subYears(15), 'more_ps_at' => now()->subYears(5)->subDay()], | ||||
|         [Prevention::MOREPS], | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => false, 'efz' => now(), 'ps_at' => now()], | ||||
|         [Prevention::VK], | ||||
|     ], | ||||
| 
 | ||||
|     [ | ||||
|         ['has_vk' => true, 'efz' => now(), 'ps_at' => now()->subYears(7)], | ||||
|         [Prevention::MOREPS], | ||||
|     ] | ||||
| ]); | ||||
| 
 | ||||
| it('testItRemembersWhenNotRememberedYet', function () { | ||||
|     Mail::fake(); | ||||
|     $form = createForm(); | ||||
|     $participant = createParticipant($form); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     $this->assertEquals(now()->format('Y-m-d'), $participant->fresh()->last_remembered_at->format('Y-m-d')); | ||||
| }); | ||||
| 
 | ||||
| it('testItDoesntRememberPastEvents', function () { | ||||
|     Mail::fake(); | ||||
|     $form = createForm(); | ||||
|     $participant = createParticipant($form); | ||||
|     $form->update(['from' => now()->subDay()]); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     $this->assertNull($participant->fresh()->last_remembered_at); | ||||
| }); | ||||
| 
 | ||||
| it('testItDoesntRememberWhenConditionDoesntMatch', function () { | ||||
|     Mail::fake(); | ||||
|     $form = createForm(); | ||||
|     $form->update(['prevention_conditions' => Condition::from(['mode' => 'all', 'ifs' => [['field' => 'vorname', 'comparator' => 'isEqual', 'value' => 'Max']]])]); | ||||
|     $participant = createParticipant($form); | ||||
|     $participant->update(['data' => [...$participant->data, 'vorname' => 'Jane']]); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     $this->assertNull($participant->fresh()->last_remembered_at); | ||||
| }); | ||||
| 
 | ||||
| it('testItRemembersWhenRememberIsDue', function () { | ||||
|     Mail::fake(); | ||||
|     $form = createForm(); | ||||
|     $participant = tap(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')); | ||||
| }); | ||||
| 
 | ||||
| it('testItDoesntRememberWhenRememberingIsNotDue', function () { | ||||
|     Mail::fake(); | ||||
|     $form = createForm(); | ||||
|     $participant = tap(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')); | ||||
| }); | ||||
| 
 | ||||
| it('testItDoesntRememberWhenFormDoesntNeedPrevention', function () { | ||||
|     Mail::fake(); | ||||
|     $form = tap(createForm(), fn($form) => $form->update(['needs_prevention' => false])); | ||||
|     $participant = createParticipant($form); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     $this->assertNull($participant->fresh()->last_remembered_at); | ||||
| }); | ||||
| 
 | ||||
| it('testItDoesntRememberWhenParticipantDoesntHaveMember', function () { | ||||
|     Mail::fake(); | ||||
|     $form = createForm(); | ||||
|     $participant = createParticipant($form); | ||||
|     $participant->member->delete(); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     $this->assertNull($participant->fresh()->last_remembered_at); | ||||
| }); | ||||
| 
 | ||||
| it('testItRemembersNonLeaders', function () { | ||||
|     Mail::fake(); | ||||
|     $form = createForm(); | ||||
|     $participant = createParticipant($form); | ||||
|     $participant->member->memberships->each->delete(); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     $this->assertNotNull($participant->fresh()->last_remembered_at); | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
| it('testItRemembersMember', function ($attrs, $preventions) { | ||||
|     Mail::fake(); | ||||
|     $form = createForm(); | ||||
|     $participant = createParticipant($form); | ||||
|     $participant->member->update($attrs); | ||||
| 
 | ||||
|     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); | ||||
|     } | ||||
| })->with('attributes'); | ||||
| 
 | ||||
| it('testItDoesntRememberParticipantThatHasNoMail', function () { | ||||
|     Mail::fake(); | ||||
|     $form = createForm(); | ||||
|     $participant = createParticipant($form); | ||||
|     $participant->update(['data' => [...$participant->data, 'email' => '']]); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     Mail::assertNotSent(PreventionRememberMail::class); | ||||
| }); | ||||
| 
 | ||||
| it('testItRendersMail', function () { | ||||
|     InvoiceSettings::fake(['from_long' => 'Stamm Beispiel']); | ||||
|     $form = createForm(); | ||||
|     $participant = createParticipant($form); | ||||
|     (new PreventionRememberMail($participant, app(PreventionSettings::class)->formmail)) | ||||
|         ->assertSeeInText($participant->member->firstname) | ||||
|         ->assertSeeInText($participant->member->lastname) | ||||
|         ->assertSeeInText('Stamm Beispiel'); | ||||
| }); | ||||
| 
 | ||||
| it('testItRendersSetttingMail', function () { | ||||
|     Mail::fake(); | ||||
|     app(PreventionSettings::class)->fill([ | ||||
|         'formmail' => EditorRequestFactory::new()->paragraphs(["lorem lala {formname} g", "{wanted}", "bbb"])->toData() | ||||
|     ])->save(); | ||||
|     $form = createForm(); | ||||
|     createParticipant($form); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     Mail::assertSent(PreventionRememberMail::class, fn($mail) => $mail->bodyText->hasAll([ | ||||
|         'lorem lala ' . $form->name, | ||||
|         'erweitertes' | ||||
|     ])); | ||||
| }); | ||||
| 
 | ||||
| it('testItAppendsTextOfForm', function () { | ||||
|     Mail::fake(); | ||||
|     app(PreventionSettings::class)->fill([ | ||||
|         'formmail' => EditorRequestFactory::new()->paragraphs(["::first::"])->toData() | ||||
|     ])->save(); | ||||
|     $form = createForm(); | ||||
|     $form->update(['prevention_text' => EditorRequestFactory::new()->paragraphs(['event'])->toData()]); | ||||
|     createParticipant($form); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     Mail::assertSent(PreventionRememberMail::class, fn($mail) => $mail->bodyText->hasAll([ | ||||
|         'event' | ||||
|     ])); | ||||
| }); | ||||
| 
 | ||||
| it('testItDoesntAppendTextTwice', function () { | ||||
|     Mail::fake(); | ||||
|     app(PreventionSettings::class)->fill(['frommail' => EditorRequestFactory::new()->paragraphs(["::first::"])->toData()])->save(); | ||||
|     tap(createForm(), function ($f) { | ||||
|         $f->update(['prevention_text' => EditorRequestFactory::new()->paragraphs(['oberhausen'])->toData()]); | ||||
|         createParticipant($f); | ||||
|     }); | ||||
|     tap(createForm(), function ($f) { | ||||
|         $f->update(['prevention_text' => EditorRequestFactory::new()->paragraphs(['siegburg'])->toData()]); | ||||
|         createParticipant($f); | ||||
|     }); | ||||
| 
 | ||||
|     PreventionRememberAction::run(); | ||||
| 
 | ||||
|     Mail::assertSent(PreventionRememberMail::class, fn($mail) => $mail->bodyText->hasAll(['oberhausen']) && !$mail->bodyText->hasAll(['siegburg'])); | ||||
| }); | ||||
| 
 | ||||
| it('testItDisplaysBodyTextInMail', function () { | ||||
|     $form = createForm(); | ||||
|     $participant = createParticipant($form); | ||||
| 
 | ||||
|     $mail = new PreventionRememberMail($participant, EditorRequestFactory::new()->paragraphs(['ggtt'])->toData()); | ||||
|     $mail->assertSeeInText('ggtt'); | ||||
| }); | ||||
| 
 | ||||
| it('displays text in yearly mail', function () { | ||||
|     $member = createMember([]); | ||||
|     $mail = new YearlyMail($member, EditorRequestFactory::new()->paragraphs(['ggtt'])->toData()); | ||||
|     $mail->assertSeeInText('ggtt'); | ||||
| }); | ||||
|  |  | |||
|  | @ -5,36 +5,33 @@ namespace Tests\Feature\Prevention; | |||
| use App\Prevention\PreventionSettings; | ||||
| use Illuminate\Foundation\Testing\DatabaseTransactions; | ||||
| use Tests\RequestFactories\EditorRequestFactory; | ||||
| use Tests\TestCase; | ||||
| 
 | ||||
| class SettingTest extends TestCase | ||||
| { | ||||
| uses(DatabaseTransactions::class); | ||||
| 
 | ||||
|     use DatabaseTransactions; | ||||
| it('testItOpensSettingsPage', function () { | ||||
|     test()->login()->loginNami(); | ||||
| 
 | ||||
|     public function testItOpensSettingsPage(): void | ||||
|     { | ||||
|         $this->login()->loginNami(); | ||||
|     test()->get('/setting/prevention')->assertComponent('setting/Prevention')->assertOk(); | ||||
| }); | ||||
| 
 | ||||
|         $this->get('/setting/prevention')->assertComponent('setting/Prevention')->assertOk(); | ||||
|     } | ||||
| it('receives settings', function () { | ||||
|     test()->login()->loginNami(); | ||||
| 
 | ||||
|     public function testItReceivesSettings(): void | ||||
|     { | ||||
|         $this->login()->loginNami(); | ||||
|     $text = EditorRequestFactory::new()->text(50, 'lorem ipsum')->toData(); | ||||
|     $yearlyMail = EditorRequestFactory::new()->text(50, 'lala dd')->toData(); | ||||
|     app(PreventionSettings::class)->fill(['formmail' => $text, 'yearlymail' => $yearlyMail])->save(); | ||||
| 
 | ||||
|         $text = EditorRequestFactory::new()->text(50, 'lorem ipsum')->toData(); | ||||
|         app(PreventionSettings::class)->fill(['formmail' => $text])->save(); | ||||
|     test()->get('/api/prevention') | ||||
|         ->assertJsonPath('data.formmail.blocks.0.data.text', 'lorem ipsum') | ||||
|         ->assertJsonPath('data.yearlymail.blocks.0.data.text', 'lala dd'); | ||||
| }); | ||||
| 
 | ||||
|         $this->get('/api/prevention') | ||||
|             ->assertJsonPath('data.formmail.blocks.0.data.text', 'lorem ipsum'); | ||||
|     } | ||||
| it('testItStoresSettings', function () { | ||||
|     test()->login()->loginNami(); | ||||
| 
 | ||||
|     public function testItStoresSettings(): void | ||||
|     { | ||||
|         $this->login()->loginNami(); | ||||
| 
 | ||||
|         $this->post('/api/prevention', ['formmail' => EditorRequestFactory::new()->text(50, 'new lorem')->create()])->assertOk(); | ||||
|         $this->assertTrue(app(PreventionSettings::class)->formmail->hasAll(['new lorem'])); | ||||
|     } | ||||
| } | ||||
|     $formmail = EditorRequestFactory::new()->text(50, 'new lorem')->create(); | ||||
|     $yearlyMail = EditorRequestFactory::new()->text(50, 'lala dd')->create(); | ||||
|     test()->post('/api/prevention', ['formmail' => $formmail, 'yearlymail' => $yearlyMail])->assertOk(); | ||||
|     test()->assertTrue(app(PreventionSettings::class)->formmail->hasAll(['new lorem'])); | ||||
|     test()->assertTrue(app(PreventionSettings::class)->yearlymail->hasAll(['lala dd'])); | ||||
| }); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue