Compare commits

..

No commits in common. "master" and "laravel10" have entirely different histories.

84 changed files with 971 additions and 1630 deletions

View File

@ -2,11 +2,10 @@ FROM php:8.3.11-fpm as php
WORKDIR /app WORKDIR /app
RUN ls /app RUN ls /app
RUN apt-get update RUN apt-get update
RUN apt-get install -y rsync libcurl3-dev apt-utils zlib1g-dev libpng-dev libicu-dev libonig-dev unzip poppler-utils libpng-dev libjpeg-dev default-mysql-client libzip-dev imagemagick libmagickwand-dev RUN apt-get install -y rsync libcurl3-dev apt-utils zlib1g-dev libpng-dev libicu-dev libonig-dev unzip poppler-utils libpng-dev libjpeg-dev default-mysql-client libzip-dev
RUN apt-get install -y --no-install-recommends texlive-base texlive-latex-base texlive-pictures texlive-latex-extra texlive-lang-german texlive-plain-generic texlive-fonts-recommended texlive-fonts-extra texlive-extra-utils RUN apt-get install -y --no-install-recommends texlive-base texlive-latex-base texlive-pictures texlive-latex-extra texlive-lang-german texlive-plain-generic texlive-fonts-recommended texlive-fonts-extra texlive-extra-utils
RUN docker-php-ext-install pdo_mysql curl exif intl mbstring pcntl zip RUN docker-php-ext-install pdo_mysql curl exif intl mbstring pcntl zip
RUN pecl install redis && docker-php-ext-enable redis RUN pecl install redis && docker-php-ext-enable redis
RUN pecl install imagick && docker-php-ext-enable imagick
RUN docker-php-ext-configure gd --with-jpeg RUN docker-php-ext-configure gd --with-jpeg
RUN docker-php-ext-install gd RUN docker-php-ext-install gd
RUN usermod -s /bin/bash www-data RUN usermod -s /bin/bash www-data

View File

@ -1,6 +0,0 @@
#!/bin/bash
docker buildx build -f .docker/base.Dockerfile .
docker image tag sha256:... zoomyboy/adrema-base
docker push zoomyboy/adrema-base

View File

@ -1,4 +1,4 @@
FROM composer:2.7.9 as composer FROM composer:2.2.7 as composer
WORKDIR /app WORKDIR /app
COPY . /app COPY . /app
RUN composer install --ignore-platform-reqs --no-dev RUN composer install --ignore-platform-reqs --no-dev

View File

@ -1,4 +1,4 @@
FROM composer:2.7.9 as composer FROM composer:2.2.7 as composer
WORKDIR /app WORKDIR /app
COPY . /app COPY . /app
RUN composer install --ignore-platform-reqs --no-dev RUN composer install --ignore-platform-reqs --no-dev

View File

@ -103,7 +103,7 @@ steps:
event: tag event: tag
- name: deploy - name: deploy
image: zoomyboy/adrema-base:latest image: php:8.1.6
environment: environment:
SSH_KEY: SSH_KEY:
from_secret: deploy_private_key from_secret: deploy_private_key

1
.gitignore vendored
View File

@ -40,4 +40,3 @@ Homestead.json
/public/sprite.svg /public/sprite.svg
/.php-cs-fixer.cache /.php-cs-fixer.cache
/groups.sql /groups.sql
/.phpunit.cache

View File

@ -1,26 +1,5 @@
# Letzte Änderungen # Letzte Änderungen
### 1.12.2
- Zuschussliste Gallier
### 1.12.1
- In Teilnehmer-Liste von Veranstaltungen kann nun sortiert und gefiltert werden.
- Formulare: Feld Registrierung von / bis
### 1.11.5
- Fix: Synchronisation von NaMi-Mitgliedern
### 1.11.4
- Fix: Nicht an Präventions-Unterlagen für vergangene Veranstaltungen erinnern
### 1.11.1
- Es kann nun auch das Feld "Datenweiterverwendung" über Adrema gepflegt werden.
### 1.10.20 ### 1.10.20
- Fixed: Bei Textfeldern wird nun die Einleitung dargestellt - Fixed: Bei Textfeldern wird nun die Einleitung dargestellt

View File

@ -50,7 +50,6 @@ class InsertMemberAction
'nationality_id' => Nationality::where('nami_id', $member->nationalityId)->firstOrFail()->id, 'nationality_id' => Nationality::where('nami_id', $member->nationalityId)->firstOrFail()->id,
'mitgliedsnr' => $member->memberId, 'mitgliedsnr' => $member->memberId,
'version' => $member->version, 'version' => $member->version,
'keepdata' => $member->keepdata,
]); ]);
} }

View File

@ -8,7 +8,6 @@ use App\Contribution\Documents\RdpNrwDocument;
use App\Contribution\Documents\CityRemscheidDocument; use App\Contribution\Documents\CityRemscheidDocument;
use App\Contribution\Documents\CitySolingenDocument; use App\Contribution\Documents\CitySolingenDocument;
use App\Contribution\Documents\CityFrankfurtMainDocument; use App\Contribution\Documents\CityFrankfurtMainDocument;
use App\Contribution\Documents\WuppertalDocument;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@ -23,7 +22,6 @@ class ContributionFactory
CityRemscheidDocument::class, CityRemscheidDocument::class,
CityFrankfurtMainDocument::class, CityFrankfurtMainDocument::class,
BdkjHesse::class, BdkjHesse::class,
WuppertalDocument::class,
]; ];
/** /**
@ -32,7 +30,7 @@ class ContributionFactory
public function compilerSelect(): Collection public function compilerSelect(): Collection
{ {
return collect($this->documents)->map(fn ($document) => [ return collect($this->documents)->map(fn ($document) => [
'title' => $document::buttonName(), 'title' => $document::getName(),
'class' => $document, 'class' => $document,
]); ]);
} }

View File

@ -72,23 +72,13 @@ class MemberData extends Data
return $this->zip . ' ' . $this->location; return $this->zip . ' ' . $this->location;
} }
public function age(): int public function age(): string
{ {
return intval($this->birthday->diffInYears(now())); return (string) $this->birthday->diffInYears(now()) ?: '';
} }
public function birthYear(): string public function birthYear(): string
{ {
return (string) $this->birthday->year; return (string) $this->birthday->year;
} }
public function birthdayHuman(): string
{
return $this->birthday->format('d.m.Y');
}
public function genderLetter(): string
{
return $this->gender->short;
}
} }

View File

@ -3,16 +3,15 @@
namespace App\Contribution\Documents; namespace App\Contribution\Documents;
use App\Contribution\Data\MemberData; use App\Contribution\Data\MemberData;
use App\Contribution\Traits\HasPdfBackground;
use App\Country; use App\Country;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Zoomyboy\Tex\Engine;
use Zoomyboy\Tex\Template;
class BdkjHesse extends ContributionDocument class BdkjHesse extends ContributionDocument
{ {
use HasPdfBackground;
/** /**
* @param Collection<int, Collection<int, MemberData>> $members * @param Collection<int, Collection<int, MemberData>> $members
*/ */
@ -26,7 +25,6 @@ class BdkjHesse extends ContributionDocument
public ?string $filename = '', public ?string $filename = '',
public string $type = 'F', public string $type = 'F',
) { ) {
$this->setEventName($eventName);
} }
public function dateFrom(): string public function dateFrom(): string
@ -116,9 +114,36 @@ class BdkjHesse extends ContributionDocument
return $member->birthYear(); return $member->birthYear();
} }
public function basename(): string
{
return 'zuschuesse-bdkj-hessen' . Str::slug($this->eventName);
}
public function view(): string
{
return 'tex.contribution.bdkj-hesse';
}
public function template(): Template
{
return Template::make('tex.templates.contribution');
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getEngine(): Engine
{
return Engine::PDFLATEX;
}
public static function getName(): string public static function getName(): string
{ {
return 'BDKJ Hessen'; return 'Für BDKJ Hessen erstellen';
} }
/** /**
@ -131,6 +156,7 @@ class BdkjHesse extends ContributionDocument
'dateUntil' => 'required|string|date_format:Y-m-d', 'dateUntil' => 'required|string|date_format:Y-m-d',
'country' => 'required|integer|exists:countries,id', 'country' => 'required|integer|exists:countries,id',
'zipLocation' => 'required|string', 'zipLocation' => 'required|string',
'eventName' => 'required|string',
]; ];
} }
} }

View File

@ -3,17 +3,16 @@
namespace App\Contribution\Documents; namespace App\Contribution\Documents;
use App\Contribution\Data\MemberData; use App\Contribution\Data\MemberData;
use App\Contribution\Traits\FormatsDates;
use App\Contribution\Traits\HasPdfBackground;
use App\Country; use App\Country;
use App\Invoice\InvoiceSettings; use App\Invoice\InvoiceSettings;
use Carbon\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Zoomyboy\Tex\Engine;
use Zoomyboy\Tex\Template;
class CityFrankfurtMainDocument extends ContributionDocument class CityFrankfurtMainDocument extends ContributionDocument
{ {
use HasPdfBackground;
use FormatsDates;
public string $fromName; public string $fromName;
/** /**
@ -29,7 +28,6 @@ class CityFrankfurtMainDocument extends ContributionDocument
public ?string $filename = '', public ?string $filename = '',
public string $type = 'F', public string $type = 'F',
) { ) {
$this->setEventName($eventName);
$this->fromName = app(InvoiceSettings::class)->from_long; $this->fromName = app(InvoiceSettings::class)->from_long;
} }
@ -63,6 +61,17 @@ class CityFrankfurtMainDocument extends ContributionDocument
); );
} }
public function dateFromHuman(): string
{
return Carbon::parse($this->dateFrom)->format('d.m.Y');
}
public function dateUntilHuman(): string
{
return Carbon::parse($this->dateUntil)->format('d.m.Y');
}
public function countryName(): string public function countryName(): string
{ {
return $this->country->name; return $this->country->name;
@ -73,9 +82,56 @@ class CityFrankfurtMainDocument extends ContributionDocument
return count($this->members); return count($this->members);
} }
public function memberShort(MemberData $member): string
{
return $member->isLeader ? 'L' : '';
}
public function memberName(MemberData $member): string
{
return $member->separatedName();
}
public function memberAddress(MemberData $member): string
{
return $member->fullAddress();
}
public function memberAge(MemberData $member): string
{
return $member->age();
}
public function basename(): string
{
return 'zuschuesse-frankfurt-' . Str::slug($this->eventName);
}
public function view(): string
{
return 'tex.contribution.city-frankfurt-main';
}
public function template(): Template
{
return Template::make('tex.templates.contribution');
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getEngine(): Engine
{
return Engine::PDFLATEX;
}
public static function getName(): string public static function getName(): string
{ {
return 'Frankfurt'; return 'Für Frankfurt erstellen';
} }
/** /**
@ -88,6 +144,7 @@ class CityFrankfurtMainDocument extends ContributionDocument
'dateUntil' => 'required|string|date_format:Y-m-d', 'dateUntil' => 'required|string|date_format:Y-m-d',
'country' => 'required|integer|exists:countries,id', 'country' => 'required|integer|exists:countries,id',
'zipLocation' => 'required|string', 'zipLocation' => 'required|string',
'eventName' => 'required|string',
]; ];
} }
} }

View File

@ -3,17 +3,15 @@
namespace App\Contribution\Documents; namespace App\Contribution\Documents;
use App\Contribution\Data\MemberData; use App\Contribution\Data\MemberData;
use App\Contribution\Traits\FormatsDates;
use App\Contribution\Traits\HasPdfBackground;
use App\Country; use App\Country;
use App\Member\Member; use App\Member\Member;
use Carbon\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Zoomyboy\Tex\Engine;
use Zoomyboy\Tex\Template;
class CityRemscheidDocument extends ContributionDocument class CityRemscheidDocument extends ContributionDocument
{ {
use HasPdfBackground;
use FormatsDates;
/** /**
* @param Collection<int, Collection<int, Member>> $leaders * @param Collection<int, Collection<int, Member>> $leaders
* @param Collection<int, Collection<int, Member>> $children * @param Collection<int, Collection<int, Member>> $children
@ -27,9 +25,17 @@ class CityRemscheidDocument extends ContributionDocument
public Collection $children, public Collection $children,
public ?string $filename = '', public ?string $filename = '',
public string $type = 'F', public string $type = 'F',
public string $eventName = '',
) { ) {
$this->setEventName($eventName); }
public function niceDateFrom(): string
{
return Carbon::parse($this->dateFrom)->format('d.m.Y');
}
public function niceDateUntil(): string
{
return Carbon::parse($this->dateUntil)->format('d.m.Y');
} }
/** /**
@ -46,7 +52,6 @@ class CityRemscheidDocument extends ContributionDocument
country: Country::where('id', $request['country'])->firstOrFail(), country: Country::where('id', $request['country'])->firstOrFail(),
leaders: $leaders->values()->toBase()->chunk(6), leaders: $leaders->values()->toBase()->chunk(6),
children: $children->values()->toBase()->chunk(20), children: $children->values()->toBase()->chunk(20),
eventName: $request['eventName'],
); );
} }
@ -65,13 +70,39 @@ class CityRemscheidDocument extends ContributionDocument
country: Country::where('id', $request['country'])->firstOrFail(), country: Country::where('id', $request['country'])->firstOrFail(),
leaders: $leaders->values()->toBase()->chunk(6), leaders: $leaders->values()->toBase()->chunk(6),
children: $children->values()->toBase()->chunk(20), children: $children->values()->toBase()->chunk(20),
eventName: $request['eventName'],
); );
} }
public function basename(): string
{
return 'zuschuesse-remscheid';
}
public function view(): string
{
return 'tex.contribution.city-remscheid';
}
public function template(): Template
{
return Template::make('tex.templates.contribution');
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getEngine(): Engine
{
return Engine::PDFLATEX;
}
public static function getName(): string public static function getName(): string
{ {
return 'Remscheid'; return 'Für Remscheid erstellen';
} }
/** /**

View File

@ -6,7 +6,9 @@ use App\Contribution\Data\MemberData;
use App\Invoice\InvoiceSettings; use App\Invoice\InvoiceSettings;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Zoomyboy\Tex\Engine; use Zoomyboy\Tex\Engine;
use Zoomyboy\Tex\Template;
class CitySolingenDocument extends ContributionDocument class CitySolingenDocument extends ContributionDocument
{ {
@ -23,7 +25,6 @@ class CitySolingenDocument extends ContributionDocument
public string $eventName, public string $eventName,
public string $type = 'F', public string $type = 'F',
) { ) {
$this->setEventName($eventName);
$this->fromName = app(InvoiceSettings::class)->from_long; $this->fromName = app(InvoiceSettings::class)->from_long;
} }
@ -73,6 +74,11 @@ class CitySolingenDocument extends ContributionDocument
return Carbon::parse($this->dateUntil)->format('d.m.Y'); return Carbon::parse($this->dateUntil)->format('d.m.Y');
} }
public function template(): Template
{
return Template::make('tex.templates.contribution');
}
public function checkboxes(): string public function checkboxes(): string
{ {
$output = ''; $output = '';
@ -88,6 +94,16 @@ class CitySolingenDocument extends ContributionDocument
return $firstRow . "\n" . $secondRow; return $firstRow . "\n" . $secondRow;
} }
public function basename(): string
{
return 'zuschuesse-solingen-' . Str::slug($this->eventName);
}
public function view(): string
{
return 'tex.contribution.city-solingen';
}
public function getEngine(): Engine public function getEngine(): Engine
{ {
return Engine::PDFLATEX; return Engine::PDFLATEX;
@ -95,7 +111,7 @@ class CitySolingenDocument extends ContributionDocument
public static function getName(): string public static function getName(): string
{ {
return 'Stadt Solingen'; return 'Für Stadt Solingen erstellen';
} }
/** /**
@ -107,6 +123,7 @@ class CitySolingenDocument extends ContributionDocument
'dateFrom' => 'required|string|date_format:Y-m-d', 'dateFrom' => 'required|string|date_format:Y-m-d',
'dateUntil' => 'required|string|date_format:Y-m-d', 'dateUntil' => 'required|string|date_format:Y-m-d',
'zipLocation' => 'required|string', 'zipLocation' => 'required|string',
'eventName' => 'required|string',
]; ];
} }
} }

View File

@ -3,12 +3,9 @@
namespace App\Contribution\Documents; namespace App\Contribution\Documents;
use Zoomyboy\Tex\Document; use Zoomyboy\Tex\Document;
use Zoomyboy\Tex\Template;
abstract class ContributionDocument extends Document abstract class ContributionDocument extends Document
{ {
private string $eventName;
abstract public static function getName(): string; abstract public static function getName(): string;
/** /**
@ -32,34 +29,8 @@ abstract class ContributionDocument extends Document
public static function globalRules(): array public static function globalRules(): array
{ {
return [ return [
'eventName' => 'required|string',
'members' => 'present|array|min:1', 'members' => 'present|array|min:1',
'members.*' => 'integer|exists:members,id', 'members.*' => 'integer|exists:members,id',
]; ];
} }
public static function buttonName(): string
{
return 'Für ' . static::getName() . ' erstellen';;
}
public function setEventName(string $eventName): void
{
$this->eventName = $eventName;
}
public function basename(): string
{
return str('Zuschüsse ')->append($this->getName())->append(' ')->append($this->eventName)->slug();
}
public function template(): Template
{
return Template::make('tex.templates.contribution');
}
public function view(): string
{
return 'tex.contribution.' . str(class_basename(static::class))->replace('Document', '')->kebab()->toString();
}
} }

View File

@ -3,16 +3,14 @@
namespace App\Contribution\Documents; namespace App\Contribution\Documents;
use App\Contribution\Data\MemberData; use App\Contribution\Data\MemberData;
use App\Contribution\Traits\FormatsDates;
use App\Contribution\Traits\HasPdfBackground;
use App\Country; use App\Country;
use Carbon\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Zoomyboy\Tex\Engine;
use Zoomyboy\Tex\Template;
class RdpNrwDocument extends ContributionDocument class RdpNrwDocument extends ContributionDocument
{ {
use HasPdfBackground;
use FormatsDates;
/** /**
* @param Collection<int, Collection<int, MemberData>> $members * @param Collection<int, Collection<int, MemberData>> $members
*/ */
@ -24,9 +22,14 @@ class RdpNrwDocument extends ContributionDocument
public Collection $members, public Collection $members,
public ?string $filename = '', public ?string $filename = '',
public string $type = 'F', public string $type = 'F',
public string $eventName = '',
) { ) {
$this->setEventName($eventName); }
public function dateRange(): string
{
return Carbon::parse($this->dateFrom)->format('d.m.Y')
. ' - '
. Carbon::parse($this->dateUntil)->format('d.m.Y');
} }
/** /**
@ -40,7 +43,6 @@ class RdpNrwDocument extends ContributionDocument
zipLocation: $request['zipLocation'], zipLocation: $request['zipLocation'],
country: Country::where('id', $request['country'])->firstOrFail(), country: Country::where('id', $request['country'])->firstOrFail(),
members: MemberData::fromModels($request['members'])->chunk(17), members: MemberData::fromModels($request['members'])->chunk(17),
eventName: $request['eventName'],
); );
} }
@ -55,7 +57,6 @@ class RdpNrwDocument extends ContributionDocument
zipLocation: $request['zipLocation'], zipLocation: $request['zipLocation'],
country: Country::where('id', $request['country'])->firstOrFail(), country: Country::where('id', $request['country'])->firstOrFail(),
members: MemberData::fromApi($request['member_data'])->chunk(17), members: MemberData::fromApi($request['member_data'])->chunk(17),
eventName: $request['eventName'],
); );
} }
@ -64,9 +65,65 @@ class RdpNrwDocument extends ContributionDocument
return $this->country->name; return $this->country->name;
} }
public function memberShort(MemberData $member): string
{
return $member->isLeader ? 'L' : '';
}
public function memberName(MemberData $member): string
{
return $member->separatedName();
}
public function memberAddress(MemberData $member): string
{
return $member->fullAddress();
}
public function memberGender(MemberData $member): string
{
if (!$member->gender) {
return '';
}
return strtolower(substr($member->gender->name, 0, 1));
}
public function memberAge(MemberData $member): string
{
return $member->age();
}
public function basename(): string
{
return 'zuschuesse-rdp-nrw';
}
public function view(): string
{
return 'tex.contribution.rdp-nrw';
}
public function template(): Template
{
return Template::make('tex.templates.contribution');
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getEngine(): Engine
{
return Engine::PDFLATEX;
}
public static function getName(): string public static function getName(): string
{ {
return 'RdP NRW'; return 'Für RdP NRW erstellen';
} }
/** /**
@ -79,6 +136,7 @@ class RdpNrwDocument extends ContributionDocument
'dateUntil' => 'required|string|date_format:Y-m-d', 'dateUntil' => 'required|string|date_format:Y-m-d',
'country' => 'required|integer|exists:countries,id', 'country' => 'required|integer|exists:countries,id',
'zipLocation' => 'required|string', 'zipLocation' => 'required|string',
'eventName' => 'required|string',
]; ];
} }
} }

View File

@ -1,79 +0,0 @@
<?php
namespace App\Contribution\Documents;
use App\Contribution\Data\MemberData;
use App\Contribution\Traits\FormatsDates;
use App\Contribution\Traits\HasPdfBackground;
use App\Country;
use Illuminate\Support\Collection;
class WuppertalDocument extends ContributionDocument
{
use HasPdfBackground;
use FormatsDates;
/**
* @param Collection<int, Collection<int, MemberData>> $members
*/
public function __construct(
public string $dateFrom,
public string $dateUntil,
public string $zipLocation,
public ?Country $country,
public Collection $members,
public ?string $filename = '',
public string $type = 'F',
public string $eventName = '',
) {
$this->setEventName($eventName);
}
/**
* {@inheritdoc}
*/
public static function fromRequest(array $request): self
{
return new self(
dateFrom: $request['dateFrom'],
dateUntil: $request['dateUntil'],
zipLocation: $request['zipLocation'],
country: Country::where('id', $request['country'])->firstOrFail(),
members: MemberData::fromModels($request['members'])->chunk(14),
eventName: $request['eventName'],
);
}
/**
* {@inheritdoc}
*/
public static function fromApiRequest(array $request): self
{
return new self(
dateFrom: $request['dateFrom'],
dateUntil: $request['dateUntil'],
zipLocation: $request['zipLocation'],
country: Country::where('id', $request['country'])->firstOrFail(),
members: MemberData::fromApi($request['member_data'])->chunk(14),
eventName: $request['eventName'],
);
}
public static function getName(): string
{
return 'Wuppertal';
}
/**
* @return array<string, mixed>
*/
public static function rules(): array
{
return [
'dateFrom' => 'required|string|date_format:Y-m-d',
'dateUntil' => 'required|string|date_format:Y-m-d',
'zipLocation' => 'required|string',
];
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace App\Contribution\Traits;
use Carbon\Carbon;
trait FormatsDates
{
public function niceDateFrom(): string
{
return Carbon::parse($this->dateFrom)->format('d.m.Y');
}
public function niceDateUntil(): string
{
return Carbon::parse($this->dateUntil)->format('d.m.Y');
}
public function dateRange(): string
{
return implode(' - ', [$this->niceDateFrom(), $this->niceDateUntil()]);
}
}

View File

@ -1,13 +0,0 @@
<?php
namespace App\Contribution\Traits;
use Zoomyboy\Tex\Engine;
trait HasPdfBackground
{
public function getEngine(): Engine
{
return Engine::PDFLATEX;
}
}

View File

@ -22,8 +22,8 @@ class FormUpdateMetaAction
return [ return [
'sorting' => 'array', 'sorting' => 'array',
'sorting.by' => 'required|string', 'sorting.0' => 'required|string',
'sorting.direction' => 'required|boolean', 'sorting.1' => 'required|string|in:asc,desc',
'active_columns' => 'array', 'active_columns' => 'array',
'active_columns.*' => ['string', Rule::in([...$form->getFields()->pluck('key')->toArray(), 'created_at', 'prevention'])] 'active_columns.*' => ['string', Rule::in([...$form->getFields()->pluck('key')->toArray(), 'created_at', 'prevention'])]
]; ];

View File

@ -6,8 +6,9 @@ use App\Form\Models\Form;
use App\Form\Models\Participant; use App\Form\Models\Participant;
use App\Form\Resources\ParticipantResource; use App\Form\Resources\ParticipantResource;
use App\Form\Scopes\ParticipantFilterScope; use App\Form\Scopes\ParticipantFilterScope;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection; use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Laravel\Scout\Builder; use Illuminate\Pagination\LengthAwarePaginator;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
class ParticipantIndexAction class ParticipantIndexAction
@ -15,22 +16,29 @@ class ParticipantIndexAction
use AsAction; use AsAction;
/** /**
* @return Builder<Participant> * @return HasMany<Participant>
*/ */
protected function getQuery(Form $form, ParticipantFilterScope $filter): Builder protected function getQuery(Form $form, ParticipantFilterScope $filter): HasMany
{ {
return $filter->setForm($form)->getQuery() return $form->participants()->withFilter($filter)->withCount('children')->with('form');
->query(fn ($q) => $q->withCount('children')->with('form')); }
/**
* @return LengthAwarePaginator<Participant>
*/
public function handle(Form $form, ParticipantFilterScope $filter): LengthAwarePaginator
{
return $this->getQuery($form, $filter)->paginate(15);
} }
public function asController(Form $form, ?int $parent = null): AnonymousResourceCollection public function asController(Form $form, ?int $parent = null): AnonymousResourceCollection
{ {
$filter = ParticipantFilterScope::fromRequest(request()->input('filter', ''))->parent($parent); $filter = ParticipantFilterScope::fromRequest(request()->input('filter'));
$data = match ($parent) { $data = match ($parent) {
null => $this->getQuery($form, $filter)->paginate(15), // initial all elements - paginate null => $this->handle($form, $filter),
-1 => $this->getQuery($form, $filter)->paginate(15), // initial root elements - parinate -1 => $this->getQuery($form, $filter)->where('parent_id', null)->paginate(15),
default => $this->getQuery($form, $filter)->get(), // specific parent element - show all default => $this->getQuery($form, $filter)->where('parent_id', $parent)->get(),
}; };
return ParticipantResource::collection($data)->additional(['meta' => ParticipantResource::meta($form)]); return ParticipantResource::collection($data)->additional(['meta' => ParticipantResource::meta($form)]);

View File

@ -17,12 +17,7 @@ class PreventionRememberAction
public function handle(): void public function handle(): void
{ {
$query = Participant::whereHas( $query = Participant::whereHas('form', fn ($form) => $form->where('needs_prevention', true))
'form',
fn ($form) => $form
->where('needs_prevention', true)
->where('from', '>=', now())
)
->where( ->where(
fn ($q) => $q fn ($q) => $q
->where('last_remembered_at', '<=', now()->subWeeks(2)) ->where('last_remembered_at', '<=', now()->subWeeks(2))

View File

@ -7,7 +7,6 @@ use App\Form\Models\Form;
use App\Form\Models\Participant; use App\Form\Models\Participant;
use App\Member\Member; use App\Member\Member;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
use Lorisleiva\Actions\ActionRequest; use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
@ -20,10 +19,6 @@ class RegisterAction
*/ */
public function handle(Form $form, array $input): Participant public function handle(Form $form, array $input): Participant
{ {
if (!$form->canRegister()) {
throw ValidationException::withMessages(['event' => 'Anmeldung zzt nicht möglich.']);
}
$memberQuery = FieldCollection::fromRequest($form, $input) $memberQuery = FieldCollection::fromRequest($form, $input)
->withNamiType() ->withNamiType()
->reduce(fn ($query, $field) => $field->namiType->performQuery($query, $field->value), (new Member())->newQuery()); ->reduce(fn ($query, $field) => $field->namiType->performQuery($query, $field->value), (new Member())->newQuery());

View File

@ -1,31 +0,0 @@
<?php
namespace App\Form\Actions;
use App\Form\Models\Form;
use Lorisleiva\Actions\Concerns\AsAction;
class UpdateParticipantSearchIndexAction
{
use AsAction;
public function handle(Form $form): void
{
if (config('scout.driver') !== 'meilisearch') {
return;
}
$form->searchableUsing()->updateIndexSettings(
$form->participantsSearchableAs(),
[
'filterableAttributes' => [...$form->getFields()->filterables()->getKeys(), 'parent-id'],
'searchableAttributes' => $form->getFields()->searchables()->getKeys(),
'sortableAttributes' => [...$form->getFields()->sortables()->getKeys(), 'id', 'created_at'],
'displayedAttributes' => [...$form->getFields()->filterables()->getKeys(), ...$form->getFields()->searchables()->getKeys(), 'id'],
'pagination' => [
'maxTotalHits' => 1000000,
]
]
);
}
}

View File

@ -1,9 +0,0 @@
<?php
namespace App\Form\Contracts;
interface Filterable
{
/** @param mixed $value */
public function filter($value): string;
}

View File

@ -2,7 +2,6 @@
namespace App\Form\Data; namespace App\Form\Data;
use App\Form\Contracts\Filterable;
use App\Form\Enums\SpecialType; use App\Form\Enums\SpecialType;
use App\Form\Fields\Field; use App\Form\Fields\Field;
use App\Form\Fields\NamiField; use App\Form\Fields\NamiField;
@ -118,27 +117,4 @@ class FieldCollection extends Collection
{ {
return $this->first(fn ($field) => $field->specialType === $specialType); return $this->first(fn ($field) => $field->specialType === $specialType);
} }
public function searchables(): self
{
return $this;
}
public function sortables(): self
{
return $this;
}
public function filterables(): self
{
return $this->filter(fn ($field) => $field instanceof Filterable);
}
/**
* @return array<int, string>
*/
public function getKeys(): array
{
return $this->map(fn ($field) => $field->key)->toArray();
}
} }

View File

@ -2,7 +2,6 @@
namespace App\Form\Fields; namespace App\Form\Fields;
use App\Form\Contracts\Filterable;
use App\Form\Matchers\BooleanMatcher; use App\Form\Matchers\BooleanMatcher;
use App\Form\Matchers\Matcher; use App\Form\Matchers\Matcher;
use App\Form\Models\Form; use App\Form\Models\Form;
@ -10,8 +9,9 @@ use App\Form\Models\Participant;
use App\Form\Presenters\BooleanPresenter; use App\Form\Presenters\BooleanPresenter;
use App\Form\Presenters\Presenter; use App\Form\Presenters\Presenter;
use Faker\Generator; use Faker\Generator;
use Illuminate\Validation\Rule;
class CheckboxField extends Field implements Filterable class CheckboxField extends Field
{ {
public bool $required; public bool $required;
public string $description; public string $description;
@ -86,11 +86,4 @@ class CheckboxField extends Field implements Filterable
{ {
return app(BooleanMatcher::class); return app(BooleanMatcher::class);
} }
public function filter($value): string
{
$asString = $value ? 'true' : 'false';
return "{$this->key} = $asString";
}
} }

View File

@ -2,7 +2,6 @@
namespace App\Form\Fields; namespace App\Form\Fields;
use App\Form\Contracts\Filterable;
use App\Form\Matchers\Matcher; use App\Form\Matchers\Matcher;
use App\Form\Matchers\SingleValueMatcher; use App\Form\Matchers\SingleValueMatcher;
use App\Form\Models\Form; use App\Form\Models\Form;
@ -10,7 +9,7 @@ use App\Form\Models\Participant;
use Faker\Generator; use Faker\Generator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
class DropdownField extends Field implements Filterable class DropdownField extends Field
{ {
public bool $required; public bool $required;
/** @var array<int, string> */ /** @var array<int, string> */
@ -88,14 +87,4 @@ class DropdownField extends Field implements Filterable
{ {
return app(SingleValueMatcher::class); return app(SingleValueMatcher::class);
} }
/** @inheritdoc */
public function filter($value): string
{
if (is_null($value)) {
return "{$this->key} IS NULL";
}
return $this->key . ' = \'' . $value . '\'';
}
} }

View File

@ -180,10 +180,4 @@ abstract class Field extends Data
{ {
return app(SingleValueMatcher::class); return app(SingleValueMatcher::class);
} }
/** @param mixed $value */
public function filter($value): string
{
return '';
}
} }

View File

@ -2,7 +2,6 @@
namespace App\Form\Fields; namespace App\Form\Fields;
use App\Form\Contracts\Filterable;
use App\Form\Matchers\Matcher; use App\Form\Matchers\Matcher;
use App\Form\Matchers\SingleValueMatcher; use App\Form\Matchers\SingleValueMatcher;
use App\Form\Models\Form; use App\Form\Models\Form;
@ -10,7 +9,7 @@ use App\Form\Models\Participant;
use Faker\Generator; use Faker\Generator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
class RadioField extends Field implements Filterable class RadioField extends Field
{ {
public bool $required; public bool $required;
/** @var array<int, string> */ /** @var array<int, string> */
@ -88,13 +87,4 @@ class RadioField extends Field implements Filterable
{ {
return app(SingleValueMatcher::class); return app(SingleValueMatcher::class);
} }
public function filter($value): string
{
if (is_null($value)) {
return "{$this->key} IS NULL";
}
return $this->key . ' = \'' . $value . '\'';
}
} }

View File

@ -2,13 +2,11 @@
namespace App\Form\Models; namespace App\Form\Models;
use App\Form\Actions\UpdateParticipantSearchIndexAction;
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\Condition;
use App\Lib\Editor\EditorData; use App\Lib\Editor\EditorData;
use App\Lib\Sorting;
use Cviebrock\EloquentSluggable\Sluggable; use Cviebrock\EloquentSluggable\Sluggable;
use Database\Factories\Form\Models\FormFactory; use Database\Factories\Form\Models\FormFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -16,6 +14,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Laravel\Scout\Searchable; use Laravel\Scout\Searchable;
use Spatie\Image\Enums\Fit; use Spatie\Image\Enums\Fit;
use Spatie\Image\Manipulations;
use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia; use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media; use Spatie\MediaLibrary\MediaCollections\Models\Media;
@ -160,7 +159,7 @@ class Form extends Model implements HasMedia
if (is_null(data_get($model->meta, 'active_columns'))) { if (is_null(data_get($model->meta, 'active_columns'))) {
$model->setAttribute('meta', [ $model->setAttribute('meta', [
'active_columns' => $model->getFields()->count() ? $model->getFields()->take(4)->pluck('key')->toArray() : null, 'active_columns' => $model->getFields()->count() ? $model->getFields()->take(4)->pluck('key')->toArray() : null,
'sorting' => Sorting::by('id'), 'sorting' => $model->getFields()->count() ? [$model->getFields()->first()->key, 'asc'] : null,
]); ]);
return; return;
} }
@ -173,32 +172,5 @@ class Form extends Model implements HasMedia
return; return;
} }
}); });
static::saved(function ($model) {
UpdateParticipantSearchIndexAction::dispatch($model);
});
}
public function participantsSearchableAs(): string
{
return config('scout.prefix') . 'forms_' . $this->id . '_participants';
}
public function defaultSorting(): Sorting
{
return Sorting::from($this->meta['sorting']);
}
public function canRegister(): bool
{
if ($this->registration_from && $this->registration_from->gt(now())) {
return false;
}
if ($this->registration_until && $this->registration_until->lt(now())) {
return false;
}
return true;
} }
} }

View File

@ -15,7 +15,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\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Laravel\Scout\Searchable;
use stdClass; use stdClass;
class Participant extends Model implements Preventable class Participant extends Model implements Preventable
@ -23,7 +22,6 @@ class Participant extends Model implements Preventable
/** @use HasFactory<ParticipantFactory> */ /** @use HasFactory<ParticipantFactory> */
use HasFactory; use HasFactory;
use Searchable;
public $guarded = []; public $guarded = [];
@ -48,6 +46,15 @@ class Participant extends Model implements Preventable
return $this->hasMany(self::class, 'parent_id'); return $this->hasMany(self::class, 'parent_id');
} }
/**
* @param Builder<self> $query
* @return Builder<self>
*/
public function scopeWithFilter(Builder $query, ParticipantFilterScope $filter): Builder
{
return $filter->apply($query);
}
/** /**
* @return BelongsTo<Member, self> * @return BelongsTo<Member, self>
*/ */
@ -103,15 +110,4 @@ class Participant extends Model implements Preventable
{ {
return 'Nachweise erforderlich für deine Anmeldung zu ' . $this->form->name; return 'Nachweise erforderlich für deine Anmeldung zu ' . $this->form->name;
} }
public function searchableAs(): string
{
return $this->form->participantsSearchableAs();
}
/** @return array<string, mixed> */
public function toSearchableArray(): array
{
return [...$this->data, 'parent-id' => $this->parent_id, 'created_at' => $this->created_at->timestamp];
}
} }

View File

@ -35,7 +35,6 @@ class FormApiResource extends JsonResource
'image' => $this->getMedia('headerImage')->first()->getFullUrl('square'), 'image' => $this->getMedia('headerImage')->first()->getFullUrl('square'),
'is_active' => $this->is_active, 'is_active' => $this->is_active,
'is_private' => $this->is_private, 'is_private' => $this->is_private,
'can_register' => $this->getModel()->canRegister(),
]; ];
} }

View File

@ -4,76 +4,32 @@ namespace App\Form\Scopes;
use App\Form\Models\Form; use App\Form\Models\Form;
use App\Form\Models\Participant; use App\Form\Models\Participant;
use App\Lib\ScoutFilter; use App\Lib\Filter;
use App\Lib\Sorting; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Laravel\Scout\Builder;
use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Mappers\SnakeCaseMapper; use Spatie\LaravelData\Mappers\SnakeCaseMapper;
/** /**
* @extends ScoutFilter<Participant> * @extends Filter<Participant>
*/ */
#[MapInputName(SnakeCaseMapper::class)] #[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)] #[MapOutputName(SnakeCaseMapper::class)]
class ParticipantFilterScope extends ScoutFilter class ParticipantFilterScope extends Filter
{ {
public static string $nan = 'deeb3ef4-d185-44b1-a4bc-0a4e7addebc3d8900c6f-a344-4afb-b54e-065ed483a7ba';
private Form $form;
/** /**
* @param array<string, mixed> $data * @param array<string, mixed> $data
* @param array<string, mixed> $options
*/ */
public function __construct( public function __construct(
public array $data = [], public array $data = [],
public string $search = '',
public array $options = [],
public ?int $parent = null,
public ?Sorting $sort = null
) { ) {
} }
public function getQuery(): Builder public static string $nan = 'deeb3ef4-d185-44b1-a4bc-0a4e7addebc3d8900c6f-a344-4afb-b54e-065ed483a7ba';
{
$this->search = $this->search ?: '';
return Participant::search($this->search, function ($engine, string $query, array $options) {
$filter = collect([]);
foreach ($this->form->getFields()->filterables() as $field) {
if ($this->data[$field->key] === static::$nan) {
continue;
}
$filter->push($field->filter($this->data[$field->key]));
}
if ($this->parent === -1) {
$filter->push('parent-id IS NULL');
}
if ($this->parent !== null && $this->parent !== -1) {
$filter->push('parent-id = ' . $this->parent);
}
$options['filter'] = $filter->map(fn ($expression) => "($expression)")->implode(' AND ');
$options['sort'] = $this->sort->toMeilisearch();
return $engine->search($query, [...$this->options, ...$options]);
})->within($this->form->participantsSearchableAs());
}
public function setForm(Form $form): self public function setForm(Form $form): self
{ {
$this->form = $form;
if (is_null($this->sort)) {
$this->sort = $this->form->defaultSorting();
}
foreach ($form->getFields() as $field) { foreach ($form->getFields() as $field) {
if (!Arr::has($this->data, $field->key)) { if (!Arr::has($this->data, $field->key)) {
data_set($this->data, $field->key, static::$nan); data_set($this->data, $field->key, static::$nan);
@ -83,10 +39,18 @@ class ParticipantFilterScope extends ScoutFilter
return $this; return $this;
} }
public function parent(?int $parent): self /**
* @inheritdoc
*/
public function apply(Builder $query): Builder
{ {
$this->parent = $parent; foreach ($this->data as $key => $value) {
if ($value === static::$nan) {
continue;
}
$query = $query->where('data->' . $key, $value);
}
return $this; return $query;
} }
} }

View File

@ -25,15 +25,6 @@ class Gender extends Model
}; };
} }
public function getShortAttribute(): string
{
return match ($this->name) {
'Männlich' => 'm',
'Weiblich' => 'w',
default => ''
};
}
public static function fromString(string $title): self public static function fromString(string $title): self
{ {
return self::firstWhere('name', $title); return self::firstWhere('name', $title);

View File

@ -6,9 +6,9 @@ use Lorisleiva\Actions\Concerns\AsAction;
use App\Invoice\Models\Invoice; use App\Invoice\Models\Invoice;
use App\Invoice\Resources\InvoiceResource; use App\Invoice\Resources\InvoiceResource;
use App\Invoice\Scopes\InvoiceFilterScope; use App\Invoice\Scopes\InvoiceFilterScope;
use Illuminate\Pagination\LengthAwarePaginator;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
use Laravel\Scout\Builder;
use Lorisleiva\Actions\ActionRequest; use Lorisleiva\Actions\ActionRequest;
class InvoiceIndexAction class InvoiceIndexAction
@ -17,11 +17,11 @@ class InvoiceIndexAction
/** /**
* @return Builder<Invoice> * @return LengthAwarePaginator<Invoice>
*/ */
public function handle(InvoiceFilterScope $filter): Builder public function handle(InvoiceFilterScope $filter): LengthAwarePaginator
{ {
return $filter->getQuery()->query(fn ($q) => $q->with('positions')); return Invoice::withFilter($filter)->with('positions')->paginate(15);
} }
public function asController(ActionRequest $request): Response public function asController(ActionRequest $request): Response
@ -32,7 +32,7 @@ class InvoiceIndexAction
$filter = InvoiceFilterScope::fromRequest($request->input('filter', '')); $filter = InvoiceFilterScope::fromRequest($request->input('filter', ''));
return Inertia::render('invoice/Index', [ return Inertia::render('invoice/Index', [
'data' => InvoiceResource::collection($this->handle($filter)->paginate(15)), 'data' => InvoiceResource::collection($this->handle($filter)),
]); ]);
} }
} }

View File

@ -8,17 +8,27 @@ use Lorisleiva\Actions\ActionRequest;
class InvoiceSettings extends LocalSettings implements Storeable class InvoiceSettings extends LocalSettings implements Storeable
{ {
public ?string $from_long; public string $from_long;
public ?string $from;
public ?string $mobile; public string $from;
public ?string $email;
public ?string $website; public string $mobile;
public ?string $address;
public ?string $place; public string $email;
public ?string $zip;
public ?string $iban; public string $website;
public ?string $bic;
public ?int $rememberWeeks; public string $address;
public string $place;
public string $zip;
public string $iban;
public string $bic;
public int $rememberWeeks;
public static function group(): string public static function group(): string
{ {

View File

@ -17,14 +17,12 @@ use Illuminate\Database\Eloquent\Collection;
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\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Laravel\Scout\Searchable;
use stdClass; use stdClass;
class Invoice extends Model class Invoice extends Model
{ {
/** @use HasFactory<InvoiceFactory> */ /** @use HasFactory<InvoiceFactory> */
use HasFactory; use HasFactory;
use Searchable;
public $guarded = []; public $guarded = [];
@ -122,6 +120,15 @@ class Invoice extends Model
->where('last_remembered_at', '<=', now()->subWeeks($weeks)); ->where('last_remembered_at', '<=', now()->subWeeks($weeks));
} }
/**
* @param Builder<self> $query
* @return Builder<self>
*/
public function scopeWithFilter(Builder $query, InvoiceFilterScope $filter): Builder
{
return $filter->apply($query);
}
public function getMailRecipient(): stdClass public function getMailRecipient(): stdClass
{ {
return (object) [ return (object) [
@ -147,20 +154,4 @@ class Invoice extends Model
]); ]);
} }
} }
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
return [
'to' => implode(', ', $this->to),
'usage' => $this->usage,
'greeting' => $this->greeting,
'mail_email' => $this->mail_email,
'status' => $this->status->value,
];
}
} }

View File

@ -5,43 +5,42 @@ namespace App\Invoice\Scopes;
use App\Invoice\Enums\InvoiceStatus; use App\Invoice\Enums\InvoiceStatus;
use App\Invoice\Models\Invoice; use App\Invoice\Models\Invoice;
use App\Lib\Filter; use App\Lib\Filter;
use App\Lib\ScoutFilter; use Illuminate\Database\Eloquent\Builder;
use Laravel\Scout\Builder;
use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Mappers\SnakeCaseMapper; use Spatie\LaravelData\Mappers\SnakeCaseMapper;
/** /**
* @extends ScoutFilter<Invoice> * @extends Filter<Invoice>
*/ */
#[MapInputName(SnakeCaseMapper::class)] #[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)] #[MapOutputName(SnakeCaseMapper::class)]
class InvoiceFilterScope extends ScoutFilter class InvoiceFilterScope extends Filter
{ {
/** /**
* @param array<int, string> $statuses * @param array<int, string> $statuses
*/ */
public function __construct( public function __construct(
public ?array $statuses = null, public ?array $statuses = null,
public ?string $search = null
) { ) {
$this->statuses = $this->statuses === null ? InvoiceStatus::defaultVisibleValues()->toArray() : $this->statuses;
} }
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function getQuery(): Builder public function apply(Builder $query): Builder
{ {
$this->search = $this->search ?: ''; $query = $query->whereIn('status', $this->statuses);
return Invoice::search($this->search, function ($engine, string $query, array $options) { return $query;
if (empty($this->statuses)) { }
$filter = 'status = "asa6aeruuni4BahC7Wei6ahm1"';
} else { /**
$filter = collect($this->statuses)->map(fn (string $status) => "status = \"$status\"")->join(' OR '); * @inheritdoc
} */
return $engine->search($query, [...$options, 'filter' => $filter]); public function toDefault(): self
}); {
$this->statuses = $this->statuses === null ? InvoiceStatus::defaultVisibleValues()->toArray() : $this->statuses;
return $this;
} }
} }

View File

@ -1,25 +0,0 @@
<?php
namespace App\Lib;
use Spatie\LaravelData\Data;
class Sorting extends Data
{
public static function by(string $by): self
{
return static::factory()->withoutMagicalCreation()->from(['by' => $by]);
}
public function __construct(public string $by, public bool $direction = false)
{
}
/**
* @return array<int, string>
*/
public function toMeilisearch(): array
{
return [$this->by . ':' . ($this->direction ? 'desc' : 'asc')];
}
}

View File

@ -46,7 +46,7 @@ class NamiPutMemberAction
'groupId' => $member->group->nami_id, 'groupId' => $member->group->nami_id,
'id' => $member->nami_id, 'id' => $member->nami_id,
'version' => $member->version, 'version' => $member->version,
'keepdata' => $member->keepdata, 'keepdata' => false,
]); ]);
$response = $api->putMember($namiMember, $activity ? $activity->nami_id : null, $subactivity ? $subactivity->nami_id : null); $response = $api->putMember($namiMember, $activity ? $activity->nami_id : null, $subactivity ? $subactivity->nami_id : null);
Member::withoutEvents(function () use ($response, $member) { Member::withoutEvents(function () use ($response, $member) {

View File

@ -58,7 +58,7 @@ class Member extends Model implements Geolocatable
/** /**
* @var array<int, string> * @var array<int, string>
*/ */
public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug', 'subscription_id', 'keepdata']; public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug', 'subscription_id'];
/** /**
* @var array<string, string> * @var array<string, string>
@ -77,7 +77,6 @@ class Member extends Model implements Geolocatable
'multiply_pv' => 'boolean', 'multiply_pv' => 'boolean',
'multiply_more_pv' => 'boolean', 'multiply_more_pv' => 'boolean',
'is_leader' => 'boolean', 'is_leader' => 'boolean',
'keepdata' => 'boolean',
'bill_kind' => BillKind::class, 'bill_kind' => BillKind::class,
'mitgliedsnr' => 'integer', 'mitgliedsnr' => 'integer',

View File

@ -83,7 +83,6 @@ class MemberRequest extends FormRequest
'other_country' => '', 'other_country' => '',
'salutation' => '', 'salutation' => '',
'comment' => '', 'comment' => '',
'keepdata' => 'boolean',
]; ];
} }

View File

@ -106,7 +106,6 @@ class MemberResource extends JsonResource
'lat' => $this->lat, 'lat' => $this->lat,
'lon' => $this->lon, 'lon' => $this->lon,
'group_name' => $this->group->name, 'group_name' => $this->group->name,
'keepdata' => $this->keepdata,
'links' => [ 'links' => [
'membership_index' => route('member.membership.index', ['member' => $this->getModel()]), 'membership_index' => route('member.membership.index', ['member' => $this->getModel()]),
'invoiceposition_index' => route('member.invoice-position.index', ['member' => $this->getModel()]), 'invoiceposition_index' => route('member.invoice-position.index', ['member' => $this->getModel()]),
@ -203,7 +202,6 @@ class MemberResource extends JsonResource
'has_svk' => false, 'has_svk' => false,
'multiply_pv' => false, 'multiply_pv' => false,
'multiply_more_pv' => false, 'multiply_more_pv' => false,
'keepdata' => false,
] ]
]; ];
} }

View File

@ -6,5 +6,3 @@ echo "create database scoutrobot;" | sudo mysql
ssh -l stammsilva zoomyboy.de "cd /usr/share/webapps/nami_silva && docker compose exec db mysqldump -udb -p$SCOUTROBOT_DB_PASSWORD db" > db.tmp ssh -l stammsilva zoomyboy.de "cd /usr/share/webapps/nami_silva && docker compose exec db mysqldump -udb -p$SCOUTROBOT_DB_PASSWORD db" > db.tmp
sudo mysql scoutrobot < db.tmp sudo mysql scoutrobot < db.tmp
rm db.tmp rm db.tmp
echo 'app(\App\Form\FormSettings::class)->fill(["registerUrl" => "http://stammsilva.test/anmeldung/{slug}/register", "clearCacheUrl" => "http://stammsilva.test/adrema/clear-cache"])->save();' | php artisan tinker

20
bin/run
View File

@ -1,17 +1,5 @@
#!/bin/bash #!/bin/bash
set -e
FILESHARE=false
WEB=false
while getopts f opt; do
case $opt in
f) FILESHARE=true ;;
w) WEB=true ;;
esac
done
tmux new-session -d -s test tmux new-session -d -s test
tmux send-keys -t test "a serve" Enter tmux send-keys -t test "a serve" Enter
@ -24,11 +12,11 @@ tmux send-keys -t test "SS start docker && duu socketi" Enter
tmux new-window -t test tmux new-window -t test
tmux send-keys -t test "nrh" Enter tmux send-keys -t test "nrh" Enter
$WEB && tmux new-window -t test tmux new-window -t test
$WEB && tmux send-keys -t test "ggwdk && cd plugins/silva/adrema/assets/vendor/adrema-form && nrd" Enter tmux send-keys -t test "ggwdk && cd plugins/silva/adrema/assets/vendor/adrema-form && nrd" Enter
$FILESHARE && tmux new-window -t test tmux new-window -t test
$FILESHARE && tmux send-keys -t test "cd tests/Fileshare && docker compose up" Enter tmux send-keys -t test "cd tests/Fileshare && docker compose up" Enter
tmux attach-session -t test tmux attach-session -t test

View File

@ -69,23 +69,6 @@ return [
], ],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'job_batches',
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Failed Queue Jobs | Failed Queue Jobs

View File

@ -1,7 +1,6 @@
<?php <?php
use App\Form\Models\Form; use App\Form\Models\Form;
use App\Invoice\Models\Invoice;
use App\Member\Member; use App\Member\Member;
return [ return [
@ -154,16 +153,7 @@ return [
'pagination' => [ 'pagination' => [
'maxTotalHits' => 1000000, 'maxTotalHits' => 1000000,
] ]
], ]
Invoice::class => [
'filterableAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'],
'searchableAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'],
'sortableAttributes' => [],
'displayedAttributes' => ['to', 'usage', 'greeting', 'mail_email', 'status', 'id'],
'pagination' => [
'maxTotalHits' => 1000000,
]
],
], ],
], ],

View File

@ -7,7 +7,6 @@ use App\Form\Models\Form;
use App\Lib\Editor\Condition; 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 Illuminate\Support\Carbon;
use Tests\Feature\Form\FormtemplateFieldRequest; use Tests\Feature\Form\FormtemplateFieldRequest;
use Tests\Feature\Form\FormtemplateSectionRequest; use Tests\Feature\Form\FormtemplateSectionRequest;
use Tests\RequestFactories\EditorRequestFactory; use Tests\RequestFactories\EditorRequestFactory;
@ -49,8 +48,8 @@ class FormFactory extends Factory
'config' => ['sections' => []], 'config' => ['sections' => []],
'from' => $this->faker->dateTimeBetween('+1 week', '+4 weeks')->format('Y-m-d H:i:s'), 'from' => $this->faker->dateTimeBetween('+1 week', '+4 weeks')->format('Y-m-d H:i:s'),
'to' => $this->faker->dateTimeBetween('+1 week', '+4 weeks')->format('Y-m-d H:i:s'), 'to' => $this->faker->dateTimeBetween('+1 week', '+4 weeks')->format('Y-m-d H:i:s'),
'registration_from' => $this->faker->dateTimeBetween(Carbon::parse('-2 weeks'), now())->format('Y-m-d H:i:s'), 'registration_from' => $this->faker->dateTimeBetween('-2 weeks', 'now')->format('Y-m-d H:i:s'),
'registration_until' => $this->faker->dateTimeBetween(now(), Carbon::parse('+2 weeks'))->format('Y-m-d H:i:s'), 'registration_until' => $this->faker->dateTimeBetween('now', '+2 weeks')->format('Y-m-d H:i:s'),
'mail_top' => EditorRequestFactory::new()->toData(), 'mail_top' => EditorRequestFactory::new()->toData(),
'mail_bottom' => EditorRequestFactory::new()->toData(), 'mail_bottom' => EditorRequestFactory::new()->toData(),
'is_active' => true, 'is_active' => true,

View File

@ -36,7 +36,6 @@ class MemberFactory extends Factory
'location' => $this->faker->city, 'location' => $this->faker->city,
'email' => $this->faker->safeEmail(), 'email' => $this->faker->safeEmail(),
'recertified_at' => null, 'recertified_at' => null,
'keepdata' => false,
]; ];
} }

View File

@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('members', function (Blueprint $table) {
$table->boolean('keepdata')->after('email_parents')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('members', function (Blueprint $table) {
$table->dropColumn('keepdata');
});
}
};

View File

@ -1,31 +0,0 @@
<?php
use App\Form\Actions\UpdateParticipantSearchIndexAction;
use App\Form\Models\Form;
use App\Lib\Sorting;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
foreach (Form::get() as $form) {
UpdateParticipantSearchIndexAction::run($form);
foreach ($form->participants as $participant) {
$participant->searchable();
}
$form->updateQuietly(['meta' => [...$form->meta, 'sorting' => Sorting::by('id')]]);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -1,27 +0,0 @@
<?php
use App\Invoice\Models\Invoice;
use Illuminate\Database\Migrations\Migration;
use Laravel\Scout\Console\SyncIndexSettingsCommand;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Artisan::call(SyncIndexSettingsCommand::class);
foreach (Invoice::get() as $invoice) {
$invoice->searchable();
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -1,30 +0,0 @@
<?php
use App\Form\Actions\UpdateParticipantSearchIndexAction;
use App\Form\Models\Form;
use App\Lib\Sorting;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
foreach (Form::get() as $form) {
UpdateParticipantSearchIndexAction::run($form);
foreach ($form->participants as $participant) {
$participant->searchable();
}
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

@ -1 +1 @@
Subproject commit 424ca30932610dcef496640e4097644ed404f13b Subproject commit 77cfe9bbc7a11ee6494b205458ac5c75ff5c166d

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="687.866" height="704.496" style="shape-rendering:geometricPrecision;text-rendering:geometricPrecision;image-rendering:optimizeQuality;fill-rule:evenodd;clip-rule:evenodd" viewBox="0 0 182.004 186.404"><defs></defs><g id="Ebene_x0020_1"><path d="m72.601.2-.6.2-.8.4-1 .6-1 1-.6 1-.6 1.6 1.2 18-39.4.4L10.6 43.2l61.001 4.601 2.6 28-2.4-1.6a12.425 12.425 0 0 1-2.2-1.4c-.916-.523-2.05-1.05-2.8-1.8l-1.8-1.2-1.6-1-1.6-1-2-1.2-1.4-.8-1.6-1-4.2-2.4-1.8-1-3-1.4-2.8-1.4-1.6-.8-2.2-1-2.4-1-2.8-1-3.2-1-4.4-1-2.8-.4h-5.4l-1 .2-2 .4-1.6.4-1 .4-1.401.6-.6.4-1 .6-1.4 1-2 2c-.262.524-.672.745-1 1.2l-.6.8c-.1.138-.3.662-.4.8l-1.2 2-.8 1.6-.6 1.4-.8 2.6-.6 1.6-.4 1.8-.4 1.8-.4 2-.4 2.4-.4 3.4-.2 3.4v10.401l.2 3.4.4 3.8.4 2.6.2 1.6.4 2.2.6 3 .4 1.6.6 2.4.4 1.4.4 1.4.2.6.4 1.2c.292.585.353 1.203.6 1.8l.4 1.2.4 1 .2.601.4 1 .6 1.6.4.8.6 1.4.4.8.4.8.6 1.2.4.8.6 1.2.6 1.2 1 1.8.4.6.6 1 .4.6 1 1.6.8 1.2.8 1.2 1 1.4 1 1.4.8 1 1 1.2.6.8 1 1.2 3 3 1 .8 1 .6 1.2.6.8.4 1 .4 2.8.6H38l3.2-.6 1.2-.4 3-1.4 1.4-.8 1-.6 2.001-1.2 1.6-1.2 1.4-1 1.6-1.2 2.6-2.2 1-.8 2.4-2 2-1.8 2.2-2 2.2-1.8c.704-.703 1.89-1.955 2.2-2.2.1-.053.874-.82 1.4-1.2l1.2-1 .2-.2.2-.4v-.6l-.2-.2-5.8-.2-2.2-.4-1.8-.6-1-.6-1-.8-1-1-.8-1-1.2-2.2-1.2-3.4-.8-2.6-.6-3.2-.6-3.4-.6-2.6-.6-3.4-.6-3.2-.8-4v-2l.8-.8h2.6l2 .4 2.4.6c1.357.542 2.688.943 4 1.6l2.2 1 1.4.6.8.4c.807.58 2.086.885 2.8 1.6l2 1.2 2.2 1.6 2.4 2.4.8 1.6 1.4 13.6h23.401l1-10.4.6-2.6 1.2-2.2 2.4-2.4.8-.6c.69-.346 1.377-.933 2-1.4.81-.54 1.721-1.25 2.6-1.6 1.04-.52 1.991-1.096 3-1.6l1.8-.8c.757-.33 1.629-.718 2.4-1l1.8-.6 1-.4 2.4-.6 2-.4h2.601l.8.8v2c-.015.296-1.42 6.984-1.6 8l-.4 2.6-.4 2-.6 3.4-.4 1.8-.4 2-.6 2.2-.6 2-.8 1.8-.6 1.2-.6 1-.8 1-1 1-2 1.4-1.8.6-2.2.4-5.8.2-.2.2v.6l.2.4c.16.222 1.13.954 1.4 1.2l1 1 2 1.8 1 1 1.4 1.2 2.6 2.2 1.6 1.6 4.8 4 1.6 1.2 8.4 5.6s2.962 1.606 3.6 1.8l2 .6 2.4.4h5.6l2.8-.6c.245-.074 2.257-.918 2.4-1l2.2-1.6 3.6-3.6c1.182-1.18 2.016-2.615 3.2-3.8l1.4-2.2 3.4-5 2.4-4.2 1.4-2.6 1-2 1.2-2.8 1-2.4 1.4-3.8.801-2.4 1-3.4.8-3.6 1.2-5.6.2-1.6.4-2.6.2-2.2.4-5V85.201l-.2-3.4-.4-3.4-.4-2.4-.2-1.4-.4-1.8-.4-1.6-.2-.8-.4-1.2-.4-1.4c-.165-.484-.78-1.904-1-2.4l-1-2.2-1-1.8-1.2-1.8-2.2-2.4c-.22-.2-3.7-2.745-3.8-2.8l-2-.8-2-.6-3-.6h-5.4l-2.8.4-3.4.8-3 .8-3 1-2.397.978-2.404 1.021-3 1.4-4 2-4.8 2.6-1.4.8-7 4.2-1.8 1.2-3.2 2-4 2.6-3.2 2.2 2.6-28.8 61.6-4.6-19.2-19.6-40.019-.2 1.418-17.8-.6-1.8-.8-1.2-1.2-1.2-1.4-.8-.6-.2-1.4-.2h-33.6l-1.2.2z"/><path d="m51.201 175.604-.6 1v.6l.2.4.4.4.4.2.8.4.8.4 1.6.6 1 .4 2.4.6 1.8.2h1.6l1-.2.6-.4.8-.6 1.8-1.6c3.438-3.437 7.332-6.59 11-9.8l-.8 14v2.4l.2 1 .2.4.2.2.8.2h27.201l.8-.2.2-.2.2-.4.2-1v-2.4l-.8-14 12 10.6.8.8.8.6.6.4 1 .2h1.6l1.8-.2 1.601-.4 1.8-.6 1-.4.6-.2.8-.4.8-.4.4-.2.4-.4.2-.4v-.6c0 .3-29.401-43.8-29.4-43.8H80.001l-28.8 42.8z"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="687.866" height="704.496" style="shape-rendering:geometricPrecision;text-rendering:geometricPrecision;image-rendering:optimizeQuality;fill-rule:evenodd;clip-rule:evenodd" viewBox="0 0 182.004 186.404"><defs><style>.fil0{fill:#000}</style></defs><g id="Ebene_x0020_1"><path class="fil0" d="m72.601.2-.6.2-.8.4-1 .6-1 1-.6 1-.6 1.6 1.2 18-39.4.4L10.6 43.2l61.001 4.601 2.6 28-2.4-1.6a12.425 12.425 0 0 1-2.2-1.4c-.916-.523-2.05-1.05-2.8-1.8l-1.8-1.2-1.6-1-1.6-1-2-1.2-1.4-.8-1.6-1-4.2-2.4-1.8-1-3-1.4-2.8-1.4-1.6-.8-2.2-1-2.4-1-2.8-1-3.2-1-4.4-1-2.8-.4h-5.4l-1 .2-2 .4-1.6.4-1 .4-1.401.6-.6.4-1 .6-1.4 1-2 2c-.262.524-.672.745-1 1.2l-.6.8c-.1.138-.3.662-.4.8l-1.2 2-.8 1.6-.6 1.4-.8 2.6-.6 1.6-.4 1.8-.4 1.8-.4 2-.4 2.4-.4 3.4-.2 3.4v10.401l.2 3.4.4 3.8.4 2.6.2 1.6.4 2.2.6 3 .4 1.6.6 2.4.4 1.4.4 1.4.2.6.4 1.2c.292.585.353 1.203.6 1.8l.4 1.2.4 1 .2.601.4 1 .6 1.6.4.8.6 1.4.4.8.4.8.6 1.2.4.8.6 1.2.6 1.2 1 1.8.4.6.6 1 .4.6 1 1.6.8 1.2.8 1.2 1 1.4 1 1.4.8 1 1 1.2.6.8 1 1.2 3 3 1 .8 1 .6 1.2.6.8.4 1 .4 2.8.6H38l3.2-.6 1.2-.4 3-1.4 1.4-.8 1-.6 2.001-1.2 1.6-1.2 1.4-1 1.6-1.2 2.6-2.2 1-.8 2.4-2 2-1.8 2.2-2 2.2-1.8c.704-.703 1.89-1.955 2.2-2.2.1-.053.874-.82 1.4-1.2l1.2-1 .2-.2.2-.4v-.6l-.2-.2-5.8-.2-2.2-.4-1.8-.6-1-.6-1-.8-1-1-.8-1-1.2-2.2-1.2-3.4-.8-2.6-.6-3.2-.6-3.4-.6-2.6-.6-3.4-.6-3.2-.8-4v-2l.8-.8h2.6l2 .4 2.4.6c1.357.542 2.688.943 4 1.6l2.2 1 1.4.6.8.4c.807.58 2.086.885 2.8 1.6l2 1.2 2.2 1.6 2.4 2.4.8 1.6 1.4 13.6h23.401l1-10.4.6-2.6 1.2-2.2 2.4-2.4.8-.6c.69-.346 1.377-.933 2-1.4.81-.54 1.721-1.25 2.6-1.6 1.04-.52 1.991-1.096 3-1.6l1.8-.8c.757-.33 1.629-.718 2.4-1l1.8-.6 1-.4 2.4-.6 2-.4h2.601l.8.8v2c-.015.296-1.42 6.984-1.6 8l-.4 2.6-.4 2-.6 3.4-.4 1.8-.4 2-.6 2.2-.6 2-.8 1.8-.6 1.2-.6 1-.8 1-1 1-2 1.4-1.8.6-2.2.4-5.8.2-.2.2v.6l.2.4c.16.222 1.13.954 1.4 1.2l1 1 2 1.8 1 1 1.4 1.2 2.6 2.2 1.6 1.6 4.8 4 1.6 1.2 8.4 5.6s2.962 1.606 3.6 1.8l2 .6 2.4.4h5.6l2.8-.6c.245-.074 2.257-.918 2.4-1l2.2-1.6 3.6-3.6c1.182-1.18 2.016-2.615 3.2-3.8l1.4-2.2 3.4-5 2.4-4.2 1.4-2.6 1-2 1.2-2.8 1-2.4 1.4-3.8.801-2.4 1-3.4.8-3.6 1.2-5.6.2-1.6.4-2.6.2-2.2.4-5V85.201l-.2-3.4-.4-3.4-.4-2.4-.2-1.4-.4-1.8-.4-1.6-.2-.8-.4-1.2-.4-1.4c-.165-.484-.78-1.904-1-2.4l-1-2.2-1-1.8-1.2-1.8-2.2-2.4c-.22-.2-3.7-2.745-3.8-2.8l-2-.8-2-.6-3-.6h-5.4l-2.8.4-3.4.8-3 .8-3 1-2.397.978-2.404 1.021-3 1.4-4 2-4.8 2.6-1.4.8-7 4.2-1.8 1.2-3.2 2-4 2.6-3.2 2.2 2.6-28.8 61.6-4.6-19.2-19.6-40.019-.2 1.418-17.8-.6-1.8-.8-1.2-1.2-1.2-1.4-.8-.6-.2-1.4-.2h-33.6l-1.2.2z"/><path class="fil0" d="m51.201 175.604-.6 1v.6l.2.4.4.4.4.2.8.4.8.4 1.6.6 1 .4 2.4.6 1.8.2h1.6l1-.2.6-.4.8-.6 1.8-1.6c3.438-3.437 7.332-6.59 11-9.8l-.8 14v2.4l.2 1 .2.4.2.2.8.2h27.201l.8-.2.2-.2.2-.4.2-1v-2.4l-.8-14 12 10.6.8.8.8.6.6.4 1 .2h1.6l1.8-.2 1.601-.4 1.8-.6 1-.4.6-.2.8-.4.8-.4.4-.2.4-.4.2-.4v-.6c0 .3-29.401-43.8-29.4-43.8H80.001l-28.8 42.8z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,34 +0,0 @@
<template>
<th @click="$emit('update:model-value')">
<div class="flex justify-between items-center">
<span v-text="label"></span>
<ui-sprite v-if="value.by === column && value.direction === false" src="chevron" class="w-3 h-3 text-primaryfg ml-2">ASC</ui-sprite>
<ui-sprite v-if="value.by === column && value.direction === true" src="chevron" class="rotate-180 w-3 h-3 text-primaryfg ml-2">DESC</ui-sprite>
</div>
</th>
</template>
<script setup>
defineEmits(['update:model-value']);
defineProps({
column: {
type: String,
default: () => '',
},
label: {
type: String,
default: () => '',
},
value: {
type: Object,
default: () => {
return {by: '', direction: false};
},
},
sortable: {
type: Boolean,
default: false,
},
});
</script>

View File

@ -28,9 +28,9 @@ export function useApiIndex(firstUrl, siteName = null) {
inner.meta.value = response.meta; inner.meta.value = response.meta;
} }
async function reloadPage(page, p = {}) { async function reloadPage(page) {
inner.meta.value.current_page = page; inner.meta.value.current_page = page;
await reload(false, p); await reload(false);
} }
function create() { function create() {

View File

@ -56,8 +56,6 @@
></f-singlefile> ></f-singlefile>
<f-text id="from" v-model="single.from" type="date" label="Von" required></f-text> <f-text id="from" v-model="single.from" type="date" label="Von" required></f-text>
<f-text id="to" v-model="single.to" type="date" label="Bis" required></f-text> <f-text id="to" v-model="single.to" type="date" label="Bis" required></f-text>
<f-text id="registration_from" v-model="single.registration_from" type="datetime-local" label="Registrierung von" required></f-text>
<f-text id="registration_until" v-model="single.registration_until" type="datetime-local" label="Registrierung bis" required></f-text>
<f-textarea <f-textarea
id="excerpt" id="excerpt"
v-model="single.excerpt" v-model="single.excerpt"
@ -66,15 +64,13 @@
:rows="5" :rows="5"
required required
></f-textarea> ></f-textarea>
</div>
<div v-if="active === 1">
<f-editor id="description" v-model="single.description" name="description" label="Beschreibung" :rows="10" required></f-editor> <f-editor id="description" v-model="single.description" name="description" label="Beschreibung" :rows="10" required></f-editor>
</div> </div>
<div v-if="active === 2"> <div v-if="active === 1">
<ui-note class="mt-2"> Sobald sich der erste Teilnehmer für die Veranstaltung angemeldet hat, kann dieses Formular nicht mehr geändert werden. </ui-note> <ui-note class="mt-2"> Sobald sich der erste Teilnehmer für die Veranstaltung angemeldet hat, kann dieses Formular nicht mehr geändert werden. </ui-note>
<form-builder v-model="single.config" :meta="meta"></form-builder> <form-builder v-model="single.config" :meta="meta"></form-builder>
</div> </div>
<div v-show="active === 3" class="grid grid-cols-[1fr_300px] gap-3"> <div v-show="active === 2" class="grid grid-cols-[1fr_300px] gap-3">
<ui-note class="mt-2 col-span-full"> <ui-note class="mt-2 col-span-full">
Hier kannst du die E-Mail anpassen, die nach der Anmeldung an den Teilnehmer verschickt wird.<br /> Hier kannst du die E-Mail anpassen, die nach der Anmeldung an den Teilnehmer verschickt wird.<br />
Es gibt dafür einen ersten E-Mail-Teil und einen zweiten E-Mail-Teil. Dazwischen werden die Daten des Teilnehmers aufgelistet.<br /> Es gibt dafür einen ersten E-Mail-Teil und einen zweiten E-Mail-Teil. Dazwischen werden die Daten des Teilnehmers aufgelistet.<br />
@ -112,14 +108,14 @@
</template> </template>
</f-multiplefiles> </f-multiplefiles>
</div> </div>
<div v-if="active === 4"> <div v-if="active === 3">
<div class="grid gap-3"> <div class="grid gap-3">
<ui-remote-resource id="export" v-model="single.export.root" label="Haupt-Ordner"></ui-remote-resource> <ui-remote-resource id="export" v-model="single.export.root" label="Haupt-Ordner"></ui-remote-resource>
<f-select id="group_by" v-model="single.export.group_by" :options="allFields" label="Gruppieren nach" name="group_by"></f-select> <f-select id="group_by" v-model="single.export.group_by" :options="allFields" label="Gruppieren nach" name="group_by"></f-select>
<f-select id="to_group_field" v-model="single.export.to_group_field" :options="allFields" label="Nach Gruppe schreiben" name="to_group_field"></f-select> <f-select id="to_group_field" v-model="single.export.to_group_field" :options="allFields" label="Nach Gruppe schreiben" name="to_group_field"></f-select>
</div> </div>
</div> </div>
<div v-show="active === 5" class="grid grid-cols-2 gap-3"> <div v-show="active === 4" class="grid grid-cols-2 gap-3">
<f-switch id="needs_prevention" v-model="single.needs_prevention" name="needs_prevention" label="Prävention"></f-switch> <f-switch id="needs_prevention" v-model="single.needs_prevention" name="needs_prevention" label="Prävention"></f-switch>
<f-editor <f-editor
id="prevention_text" id="prevention_text"
@ -211,7 +207,7 @@ const fileSettingPopup = ref(null);
const active = ref(0); const active = ref(0);
const activeMailTab = ref(0); const activeMailTab = ref(0);
const tabs = [{ title: 'Allgemeines' }, { title: 'Beschreibung' }, { title: 'Formular' }, { title: 'Bestätigungs-E-Mail' }, { title: 'Export' }, { title: 'Prävention' }]; const tabs = [{ title: 'Allgemeines' }, { title: 'Formular' }, { title: 'Bestätigungs-E-Mail' }, { title: 'Export' }, { title: 'Prävention' }];
const mailTabs = [{ title: 'vor Daten' }, { title: 'nach Daten' }]; const mailTabs = [{ title: 'vor Daten' }, { title: 'nach Daten' }];
const allFields = computed(() => { const allFields = computed(() => {

View File

@ -23,7 +23,6 @@
</ui-popup> </ui-popup>
<page-filter breakpoint="lg"> <page-filter breakpoint="lg">
<template #buttons> <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> <ui-icon-button icon="plus" @click="editing = {participant: null, preview: JSON.stringify(meta.form_config)}">Hinzufügen</ui-icon-button>
<f-switch v-if="meta.has_nami_field" id="group_participants" v-model="groupParticipants" label="Gruppieren" size="sm" name="group_participants"></f-switch> <f-switch v-if="meta.has_nami_field" id="group_participants" v-model="groupParticipants" label="Gruppieren" size="sm" name="group_participants"></f-switch>
<f-multipleselect id="active_columns" v-model="activeColumnsConfig" :options="meta.columns" label="Aktive Spalten" size="sm"></f-multipleselect> <f-multipleselect id="active_columns" v-model="activeColumnsConfig" :options="meta.columns" label="Aktive Spalten" size="sm"></f-multipleselect>
@ -69,15 +68,7 @@
</page-filter> </page-filter>
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm"> <table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm">
<thead> <thead>
<ui-th <th v-for="column in activeColumns" :key="column.id" v-text="column.name"></th>
v-for="column in activeColumns"
:key="column.id"
:column="column.id"
:label="column.name"
:value="getSort"
sortable
@update:model-value="setSort(column.id, {filter: toFilterString(innerFilter)})"
></ui-th>
<th></th> <th></th>
</thead> </thead>
@ -126,7 +117,7 @@
</template> </template>
</table> </table>
<div class="px-6"> <div class="px-6">
<ui-pagination class="mt-4" :value="meta" @reload="reloadPage($event, {filter: toFilterString(innerFilter)})"></ui-pagination> <ui-pagination class="mt-4" :value="meta" @reload="reloadPage"></ui-pagination>
</div> </div>
</div> </div>
</template> </template>
@ -182,13 +173,6 @@ var { meta, data, reload, reloadPage, axios, remove, toFilterString, url, update
const activeColumns = computed(() => meta.value.columns.filter((c) => meta.value.form_meta.active_columns.includes(c.id))); const activeColumns = computed(() => meta.value.columns.filter((c) => meta.value.form_meta.active_columns.includes(c.id)));
const getSort = computed(() => innerFilter.value.sort);
async function setSort(column) {
innerFilter.value.sort = getSort.value.by === column ? {by: column, direction: !getSort.value.direction} : {by: column, direction: false};
sortingConfig.value = innerFilter.value.sort;
}
const activeColumnsConfig = computed({ const activeColumnsConfig = computed({
get: () => meta.value.form_meta.active_columns, get: () => meta.value.form_meta.active_columns,
set: async (v) => { set: async (v) => {
@ -201,18 +185,6 @@ const activeColumnsConfig = computed({
}, },
}); });
const sortingConfig = computed({
get: () => meta.value.form_meta.sorting,
set: async (v) => {
const response = await axios.patch(meta.value.links.update_form_meta, {
...meta.value.form_meta,
sorting: v,
});
meta.value.form_meta = response.data;
},
});
async function handleDelete() { async function handleDelete() {
await remove(deleting.value); await remove(deleting.value);
deleting.value = null; deleting.value = null;

View File

@ -74,7 +74,6 @@
</ui-popup> </ui-popup>
<page-filter breakpoint="xl" :filterable="false"> <page-filter breakpoint="xl" :filterable="false">
<template #buttons> <template #buttons>
<f-text id="search" :model-value="getFilter('search')" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text>
<f-multipleselect <f-multipleselect
id="statuses" id="statuses"
:options="meta.statuses" :options="meta.statuses"
@ -115,7 +114,6 @@
<div class="flex space-x-2"> <div class="flex space-x-2">
<ui-action-button tooltip="Anschauen" :href="invoice.links.pdf" class="btn-info" icon="eye" blank></ui-action-button> <ui-action-button tooltip="Anschauen" :href="invoice.links.pdf" class="btn-info" icon="eye" blank></ui-action-button>
<ui-action-button tooltip="Erinnerung anschauen" :href="invoice.links.rememberpdf" class="btn-info" icon="document" blank></ui-action-button> <ui-action-button tooltip="Erinnerung anschauen" :href="invoice.links.rememberpdf" class="btn-info" icon="document" blank></ui-action-button>
<ui-action-button tooltip="Als Bezahlt markieren" class="btn-warning" icon="money" blank @click.prevent="markAsPaid(invoice)"></ui-action-button>
<ui-action-button :data-cy="`edit-button-${invoice.id}`" tooltip="Bearbeiten" class="btn-warning" icon="pencil" @click.prevent="edit(invoice)"></ui-action-button> <ui-action-button :data-cy="`edit-button-${invoice.id}`" tooltip="Bearbeiten" class="btn-warning" icon="pencil" @click.prevent="edit(invoice)"></ui-action-button>
<ui-action-button tooltip="Löschen" class="btn-danger" icon="trash" @click.prevent="deleting = invoice"></ui-action-button> <ui-action-button tooltip="Löschen" class="btn-danger" icon="trash" @click.prevent="deleting = invoice"></ui-action-button>
</div> </div>
@ -145,9 +143,4 @@ async function sendMassstore() {
await axios.post(meta.value.links['mass-store'], massstore.value); await axios.post(meta.value.links['mass-store'], massstore.value);
massstore.value = null; massstore.value = null;
} }
async function markAsPaid(invoice) {
await axios.patch(invoice.links.update, {...invoice, status: 'Rechnung beglichen'});
await reloadPage();
}
</script> </script>

View File

@ -58,8 +58,7 @@
<f-select id="subscription_id" v-model="inner.subscription_id" :options="meta.subscriptions" label="Beitrag" name="subscription_id" size="sm"></f-select> <f-select id="subscription_id" v-model="inner.subscription_id" :options="meta.subscriptions" label="Beitrag" name="subscription_id" size="sm"></f-select>
<f-switch id="has_nami" v-model="inner.has_nami" name="has_nami" size="sm" label="In Nami eintragen"></f-switch> <f-switch id="has_nami" v-model="inner.has_nami" name="has_nami" size="sm" label="In Nami eintragen"></f-switch>
<f-switch id="send_newspaper" v-model="inner.send_newspaper" name="send_newspaper" label="Mittendrin versenden" size="sm"></f-switch> <f-switch id="send_newspaper" v-model="inner.send_newspaper" name="send_newspaper" label="Mittendrin versenden" size="sm"></f-switch>
<f-switch id="keepdata" v-model="inner.keepdata" name="keepdata" label="Datenweiterverwendung" size="sm"></f-switch> <f-text id="joined_at" v-model="inner.joined_at" class="sm:col-span-2" type="date" label="Eintrittsdatum" size="sm" required></f-text>
<f-text id="joined_at" v-model="inner.joined_at" type="date" label="Eintrittsdatum" size="sm" required></f-text>
<f-textarea id="comment" v-model="inner.comment" :rows="3" class="col-span-2" label="Kommentar" size="sm"></f-textarea> <f-textarea id="comment" v-model="inner.comment" :rows="3" class="col-span-2" label="Kommentar" size="sm"></f-textarea>
<div v-if="mode === 'create' || (original.has_nami === false && inner.has_nami === true)" class="contents"> <div v-if="mode === 'create' || (original.has_nami === false && inner.has_nami === true)" class="contents">
<f-select <f-select

View File

@ -18,15 +18,15 @@
\node[anchor=base west, text width=205.3mm] at (52.9mm,31.0mm) {\bfseries{\large{<<<!!$fromName!!>>>}}}; %Feld: Jugendverband/-Gruppe \node[anchor=base west, text width=205.3mm] at (52.9mm,31.0mm) {\bfseries{\large{<<<!!$fromName!!>>>}}}; %Feld: Jugendverband/-Gruppe
\node[anchor=base west, text width=215.9mm] at (41.3mm,38.8mm) {\bfseries{\large{<<<!!$eventName!!>>>}}}; %Feld: Art der Maßnahme \node[anchor=base west, text width=215.9mm] at (41.3mm,38.8mm) {\bfseries{\large{<<<!!$eventName!!>>>}}}; %Feld: Art der Maßnahme
\node[anchor=base west, text width=104.8mm] at (17.5mm,47.0mm) {\bfseries{\large{<<<!!$zipLocation!!>>>, <<<!!$countryName!!>>>}}}; \node[anchor=base west, text width=104.8mm] at (17.5mm,47.0mm) {\bfseries{\large{<<<!!$zipLocation!!>>>, <<<!!$countryName!!>>>}}};
\node[anchor=base west, text width=41.3mm, align=center] at (170.7mm,47.0mm) {\bfseries{\large{<<<!!$niceDateFrom()!!>>>}}}; \node[anchor=base west, text width=41.3mm, align=center] at (170.7mm,47.0mm) {\bfseries{\large{<<<!!$dateFromHuman()!!>>>}}};
\node[anchor=base west, text width=38.4mm, align=center] at (219.9mm,47.0mm) {\bfseries{\large{<<<!!$niceDateUntil()!!>>>}}}; \node[anchor=base west, text width=38.4mm, align=center] at (219.9mm,47.0mm) {\bfseries{\large{<<<!!$dateUntilHuman()!!>>>}}};
@foreach($chunk as $j => $member) @foreach($chunk as $j => $member)
\node[anchor=base, text width=4mm, align=center] at ($(8.0mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$member->isLeader ? 'L' : ''>>>}; \node[anchor=base, text width=4mm, align=center] at ($(8.0mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$memberShort($member)>>>};
\node[anchor=base, text width=6mm, align=center] at ($(13.0mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$j+1>>>}; \node[anchor=base, text width=6mm, align=center] at ($(13.0mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$j+1>>>};
\node[anchor=base, text width=67.5mm, align=center] at ($(50.65mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$member->separatedName()>>>}; \node[anchor=base, text width=67.5mm, align=center] at ($(50.65mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$memberName($member)>>>};
\node[anchor=base, text width=14.6mm, align=center] at ($(92.2mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$member->age()>>>}; \node[anchor=base, text width=14.6mm, align=center] at ($(92.2mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$memberAge($member)>>>};
\node[anchor=base, text width=79.4mm, align=center] at ($(139.7mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$member->fullAddress()>>>}; \node[anchor=base, text width=79.4mm, align=center] at ($(139.7mm, 69.0mm + 8.05mm * <<<$j%15>>>)$) {<<<$memberAddress($member)>>>};
@endforeach @endforeach
\node[anchor=base, text width=23.0mm, align=center] at (278.2mm,196.1mm) {Seite <<<!!$i + 1!!>>> von <<<!!$pages!!>>>}; \node[anchor=base, text width=23.0mm, align=center] at (278.2mm,196.1mm) {Seite <<<!!$i + 1!!>>> von <<<!!$pages!!>>>};

View File

@ -23,11 +23,11 @@
@foreach($chunk as $i => $member) @foreach($chunk as $i => $member)
\node[anchor=base, text width=7.75mm, align=center] at ($(16.35mm, 76.6mm + 7mm * <<<$i % 17>>>)$) {<<<$i+1>>>}; \node[anchor=base, text width=7.75mm, align=center] at ($(16.35mm, 76.6mm + 7mm * <<<$i % 17>>>)$) {<<<$i+1>>>};
\node[anchor=base, text width=18mm, align=center] at ($(32.55mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$member->isLeader ? 'L' : ''>>>}; \node[anchor=base, text width=18mm, align=center] at ($(32.55mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberShort($member)>>>};
\node[anchor=base, text width=70mm, align=center] at ($(80.25mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$member->separatedName()>>>}; \node[anchor=base, text width=70mm, align=center] at ($(80.25mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberName($member)>>>};
\node[anchor=base, text width=118mm, align=center] at ($(178.25mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$member->fullAddress()>>>}; \node[anchor=base, text width=118mm, align=center] at ($(178.25mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberAddress($member)>>>};
\node[anchor=base, text width=16mm, align=center] at ($(249.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$member->genderLetter()>>>}; \node[anchor=base, text width=16mm, align=center] at ($(249.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberGender($member)>>>};
\node[anchor=base, text width=16mm, align=center] at ($(269.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$member->age()>>>}; \node[anchor=base, text width=16mm, align=center] at ($(269.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberAge($member)>>>};
@endforeach @endforeach
\end{tikzpicture} \end{tikzpicture}

View File

@ -1,39 +0,0 @@
\documentclass[a4paper,landscape]{article}
\usepackage[landscape,top=0cm,left=0cm,bottom=0cm,right=0cm]{geometry}
\usepackage{tikz}
\usepackage{background}
\usepackage{blindtext}
\usetikzlibrary{matrix, shapes.misc, calc}
\pagestyle{empty}
\setlength{\parindent}{0cm}
\backgroundsetup{scale = 1, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {wuppertal.pdf}}}
\begin{document}
\noindent \sffamily
@foreach($members as $chunk)
\begin{tikzpicture}[remember picture,overlay,yscale=-1]
\node[anchor=base west] at (17mm,29.62mm) {\bfseries{\small{<<<$niceDateFrom>>>}}};
\node[anchor=base west] at (34mm,29.62mm) {\bfseries{\small{<<<$niceDateUntil>>>}}};
\node[] at (203mm,15.55mm) {\bfseries{\small{<<<$zipLocation>>>}}};
@foreach($chunk as $i => $member)
\node[anchor=center, text width=9.75mm, align=center] at ($(19.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$i+1>>>};
\node[anchor=center, text width=41.75mm, align=center] at ($(48.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$member->lastname>>>};
\node[anchor=center, text width=37.75mm, align=center] at ($(91.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$member->firstname>>>};
\node[anchor=center, text width=45.75mm, align=center] at ($(135.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$member->address>>>};
\node[anchor=center, text width=26.75mm, align=center] at ($(174.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$member->city()>>>};
\node[anchor=center, text width=19.75mm, align=center] at ($(199.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$member->birthdayHuman()>>>};
\node[anchor=center, text width=7.75mm, align=center] at ($(216.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$member->genderLetter()>>>};
\node[anchor=center, text width=7.75mm, align=center] at ($(276.35mm, 59.7mm + 9.2mm * <<<$i%14>>>)$) {<<<$member->isLeader ? 'GL' : 'T'>>>};
@endforeach
\end{tikzpicture}
\pagebreak
@endforeach
\end{document}

View File

@ -0,0 +1,22 @@
<?php
namespace Tests;
use Illuminate\Contracts\Console\Kernel;
trait CreatesApplication
{
/**
* Creates the application.
*
* @return \Illuminate\Foundation\Application
*/
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
return $app;
}
}

View File

@ -3,12 +3,14 @@
namespace Tests\EndToEnd\Form; namespace Tests\EndToEnd\Form;
use App\Form\Models\Form; use App\Form\Models\Form;
use App\Membership\TestersBlock;
use App\Subactivity; use App\Subactivity;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Tests\Feature\Form\FormtemplateSectionRequest; use Tests\Feature\Form\FormtemplateSectionRequest;
use Tests\RequestFactories\EditorRequestFactory; use Tests\RequestFactories\EditorRequestFactory;
use Tests\TestCase;
uses(FormTestCase::class); uses(FormTestCase::class);
uses(DatabaseTransactions::class); uses(DatabaseTransactions::class);
@ -40,24 +42,11 @@ it('testItDisplaysForms', function () {
->assertJsonPath('data.0.dates', '05.05.2023 - 07.06.2023') ->assertJsonPath('data.0.dates', '05.05.2023 - 07.06.2023')
->assertJsonPath('data.0.from_human', '05.05.2023') ->assertJsonPath('data.0.from_human', '05.05.2023')
->assertJsonPath('data.0.to_human', '07.06.2023') ->assertJsonPath('data.0.to_human', '07.06.2023')
->assertJsonPath('data.0.can_register', true)
->assertJsonPath('meta.per_page', 15) ->assertJsonPath('meta.per_page', 15)
->assertJsonPath('meta.base_url', url('')) ->assertJsonPath('meta.base_url', url(''))
->assertJsonPath('meta.total', 1); ->assertJsonPath('meta.total', 1);
}); });
it('displays registration not possible', function () {
Storage::fake('temp');
$this->loginNami()->withoutExceptionHandling();
Form::factory()
->registrationFrom(now()->addDay())
->withImage('headerImage', 'lala-2.jpg')
->create();
sleep(1);
$this->get('/api/form?perPage=15')->assertJsonPath('data.0.can_register', false);
});
it('testItDisplaysDefaultValueOfField', function () { it('testItDisplaysDefaultValueOfField', function () {
Storage::fake('temp'); Storage::fake('temp');
$this->loginNami()->withoutExceptionHandling(); $this->loginNami()->withoutExceptionHandling();

View File

@ -1,309 +0,0 @@
<?php
namespace Tests\Feature\Form;
use App\Form\Fields\TextField;
use App\Form\Models\Form;
use App\Form\Models\Participant;
use App\Form\Scopes\ParticipantFilterScope;
use App\Group;
use App\Member\Member;
use Carbon\Carbon;
use Tests\EndToEndTestCase;
use Tests\Lib\CreatesFormFields;
uses(EndToEndTestCase::class);
uses(CreatesFormFields::class);
it('testItShowsParticipantsAndColumns', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$group = Group::factory()->innerName('Stamm')->create();
$form = Form::factory()
->has(Participant::factory()->state(['member_id' => 55])->data(['vorname' => 'Max', 'select' => ['A', 'B'], 'stufe' => 'Pfadfinder', 'test1' => '', 'test2' => '', 'test3' => '', 'birthday' => '1991-04-20', 'bezirk' => $group->id]))
->fields([
$this->textField('vorname')->name('Vorname'),
$this->checkboxesField('select')->options(['A', 'B', 'C']),
$this->dropdownField('stufe')->options(['Wölfling', 'Jungpfadfinder', 'Pfadfinder']),
$this->textField('test1')->name('Test 1'),
$this->textField('test2')->name('Test 2'),
$this->textField('test3')->name('Test 3'),
$this->dateField('birthday')->name('Geburtsdatum'),
$this->groupField('bezirk')->name('bezirk'),
])
->create();
sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertOk()
->assertJsonPath('data.0.id', $form->participants->first()->id)
->assertJsonPath('data.0.vorname', 'Max')
->assertJsonPath('data.0.vorname_display', 'Max')
->assertJsonPath('data.0.stufe', 'Pfadfinder')
->assertJsonPath('data.0.bezirk', $group->id)
->assertJsonPath('data.0.member_id', 55)
->assertJsonPath('data.0.bezirk_display', 'Stamm')
->assertJsonPath('data.0.birthday_display', '20.04.1991')
->assertJsonPath('data.0.birthday', '1991-04-20')
->assertJsonPath('data.0.select', ['A', 'B'])
->assertJsonPath('data.0.select_display', 'A, B')
->assertJsonPath('data.0.links.destroy', route('participant.destroy', ['participant' => $form->participants->first()]))
->assertJsonPath('data.0.links.assign', route('participant.assign', ['participant' => $form->participants->first()]))
->assertJsonPath('data.0.links.fields', route('participant.fields', ['participant' => $form->participants->first()]))
->assertJsonPath('data.0.links.update', route('participant.update', ['participant' => $form->participants->first()]))
->assertJsonPath('meta.columns.0.name', 'Vorname')
->assertJsonPath('meta.columns.0.base_type', class_basename(TextField::class))
->assertJsonPath('meta.columns.0.id', 'vorname')
->assertJsonPath('meta.columns.6.display_attribute', 'birthday_display')
->assertJsonPath('meta.columns.0.display_attribute', 'vorname_display')
->assertJsonPath('meta.form_meta.active_columns', ['vorname', 'select', 'stufe', 'test1'])
->assertJsonPath('meta.has_nami_field', false)
->assertJsonPath('meta.links.update_form_meta', route('form.update-meta', ['form' => $form]))
->assertJsonPath('meta.links.store_participant', route('form.participant.store', ['form' => $form]))
->assertJsonPath('meta.form_meta.sorting', ['by' => 'id', 'direction' => false])
->assertJsonPath('meta.form_config.sections.0.fields.0.key', 'vorname');
});
it('testItShowsEmptyFilters', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->checkboxField('check')->name('Checked')])->create();
sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertOk()
->assertJsonPath('meta.filters.0.name', 'Checked')
->assertJsonPath('meta.filters.0.key', 'check')
->assertJsonPath('meta.filters.0.base_type', 'CheckboxField')
->assertJsonPath('meta.default_filter_value', ParticipantFilterScope::$nan);
$this->callFilter('form.participant.index', ['data' => ['check' => null]], ['form' => $form])->assertHasJsonPath('meta.filter.data.check')->assertJsonPath('meta.filter.data.check', null);
$this->callFilter('form.participant.index', ['data' => ['check' => 'A']], ['form' => $form])->assertJsonPath('meta.filter.data.check', 'A');
$this->callFilter('form.participant.index', ['data' => []], ['form' => $form])->assertJsonPath('meta.filter.data.check', ParticipantFilterScope::$nan);
});
it('sorts by active colums sorting by default', function (array $sorting, string $by, bool $direction) {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([
$this->checkboxField('check'),
$this->checkboxField('vorname'),
])->create();
$form->update(['meta' => ['active_columns' => [], 'sorting' => $sorting]]);
sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertOk()
->assertJsonPath('meta.filter.sort.by', $by)
->assertJsonPath('meta.filter.sort.direction', $direction);
})->with([
[['by' => 'vorname', 'direction' => true], 'vorname', true],
[['by' => 'created_at', 'direction' => true], 'created_at', true],
]);
it('testItDisplaysHasNamiField', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->namiField('mitglieder')])->create();
sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $form])->assertJsonPath('meta.has_nami_field', true);
});
it('testItFiltersParticipantsByCheckboxValue', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->checkboxField('check')])
->has(Participant::factory()->data(['check' => true])->count(1))
->has(Participant::factory()->data(['check' => false])->count(2))
->create();
sleep(2);
$this->callFilter('form.participant.index', ['data' => []], ['form' => $form])
->assertJsonCount(3, 'data');
$this->callFilter('form.participant.index', ['data' => ['check' => ParticipantFilterScope::$nan]], ['form' => $form])
->assertJsonCount(3, 'data');
$this->callFilter('form.participant.index', ['data' => ['check' => true]], ['form' => $form])
->assertJsonCount(1, 'data');
$this->callFilter('form.participant.index', ['data' => ['check' => false]], ['form' => $form])
->assertJsonCount(2, 'data');
});
it('test it handles full text search', function (array $memberAttributes, string $search, bool $includes) {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()
->has(Participant::factory()->data(['vorname' => 'Max', 'select' => 'Pfadfinder', ...$memberAttributes]))
->fields([
$this->textField('vorname')->name('Vorname'),
$this->checkboxesField('select')->options(['Wölflinge', 'Pfadfinder']),
])
->create();
sleep(2);
$this->callFilter('form.participant.index', ['search' => $search], ['form' => $form])
->assertJsonCount($includes ? 1 : 0, 'data');
})->with([
[['vorname' => 'Max'], 'Max', true],
[['vorname' => 'Jane'], 'Max', false],
[['select' => 'Pfadfinder'], 'Pfadfinder', true],
[['select' => 'Pfadfinder'], 'Rov', false],
[['select' => 'Wölflinge'], 'Wölflinge', true],
[['select' => 'Wölflinge'], 'Wölf', true],
[['vorname' => 'Max', 'nachname' => 'Muster'], 'Max Muster', true],
[['vorname' => 'Max', 'nachname' => 'Muster'], 'Jane Doe', false],
]);
it('testItFiltersParticipantsByDropdownValue', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->dropdownField('drop')->options(['A', 'B'])])
->has(Participant::factory()->data(['drop' => null])->count(1))
->has(Participant::factory()->data(['drop' => 'A'])->count(2))
->has(Participant::factory()->data(['drop' => 'B'])->count(4))
->create();
sleep(2);
$this->callFilter('form.participant.index', ['data' => ['drop' => ParticipantFilterScope::$nan]], ['form' => $form])
->assertJsonCount(7, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => null]], ['form' => $form])
->assertJsonCount(1, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => 'A']], ['form' => $form])
->assertJsonCount(2, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => 'B']], ['form' => $form])
->assertJsonCount(4, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => 'Z*Z']], ['form' => $form])
->assertJsonCount(0, 'data');
});
it('testItFiltersParticipantsByRadioValue', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->radioField('drop')->options(['A', 'B'])])
->has(Participant::factory()->data(['drop' => null])->count(1))
->has(Participant::factory()->data(['drop' => 'A'])->count(2))
->has(Participant::factory()->data(['drop' => 'B'])->count(4))
->create();
sleep(2);
$this->callFilter('form.participant.index', ['data' => ['drop' => ParticipantFilterScope::$nan]], ['form' => $form])
->assertJsonCount(7, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => 'A']], ['form' => $form])
->assertJsonCount(2, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => 'B']], ['form' => $form])
->assertJsonCount(4, 'data');
});
it('testItPresentsNamiField', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()
->has(Participant::factory()->data(['mitglieder' => [['id' => 393], ['id' => 394]]]))
->has(Participant::factory()->nr(393)->data(['mitglieder' => []]))
->has(Participant::factory()->nr(394)->data(['mitglieder' => []]))
->fields([
$this->namiField('mitglieder'),
])
->create();
sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertJsonPath('data.0.mitglieder_display', '393, 394');
});
it('testItShowsRegisteredAtColumnAndAttribute', function () {
Carbon::setTestNow(Carbon::parse('2023-03-05 06:00:00'));
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()
->has(Participant::factory()->data(['vorname' => 'Max']))
->fields([
$this->textField('vorname')->name('Vorname'),
])
->create();
sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertJsonPath('data.0.vorname', 'Max')
->assertJsonPath('data.0.vorname_display', 'Max')
->assertJsonPath('data.0.created_at', '2023-03-05 06:00:00')
->assertJsonPath('data.0.created_at_display', '05.03.2023')
->assertJsonPath('meta.columns.1.name', 'Registriert am')
->assertJsonPath('meta.columns.1.id', 'created_at')
->assertJsonPath('meta.columns.1.display_attribute', 'created_at_display');
});
it('testItShowsOnlyParentParticipantsWhenFilterEnabled', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->create();
$participant = Participant::factory()
->has(Participant::factory()->for($form)->count(2), 'children')
->for($form)
->create();
sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $form])->assertJsonCount(3, 'data');
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => -1])->assertJsonCount(1, 'data');
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => $participant->id])->assertJsonCount(2, 'data');
});
it('testItShowsChildrenCount', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->create();
$participant = Participant::factory()
->has(Participant::factory()->for($form)->count(2), 'children')
->for($form)
->create();
Participant::factory()->for($form)->create();
sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => -1])
->assertJsonPath('data.0.children_count', 2)
->assertJsonPath('data.1.children_count', 0)
->assertJsonPath('data.0.links.children', route('form.participant.index', ['form' => $form, 'parent' => $participant->id]))
->assertJsonPath('meta.current_page', 1);
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => $participant->id])->assertJsonPath('data.0.children_count', 0);
});
it('testItShowsPreventionState', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$participant = Participant::factory()->data(['vorname' => 'Max'])
->for(Member::factory()->defaults()->state(['efz' => null]))
->for(Form::factory())
->create();
sleep(2);
$this->callFilter('form.participant.index', [], ['form' => $participant->form])
->assertJsonPath('data.0.prevention_items.0.letter', 'F')
->assertJsonPath('data.0.prevention_items.0.value', false)
->assertJsonPath('data.0.prevention_items.0.tooltip', 'erweitertes Führungszeugnis nicht vorhanden');
});
it('test it orders participants by value', function (array $values, array $sorting, array $expected) {
list($key, $direction) = $sorting;
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()
->fields([
$this->textField('vorname')->name('Vorname'),
$this->checkboxesField('select')->options(['Wölflinge', 'Pfadfinder']),
]);
foreach ($values as $value) {
$form = $form->has(Participant::factory()->data(['vorname' => 'Max', 'select' => 'Pfadfinder', $key => $value]));
}
$form = $form->create();
sleep(2);
$response = $this->callFilter('form.participant.index', ['sort' => ['by' => $key, 'direction' => $direction]], ['form' => $form]);
$response->assertJsonPath('meta.filter.sort.by', $key);
$response->assertJsonPath('meta.filter.sort.direction', $direction);
foreach ($expected as $index => $value) {
$response->assertJsonPath("data.{$index}.{$key}", $value);
}
})->with([
[
['Anna', 'Sarah', 'Ben'],
['vorname', false],
['Anna', 'Ben', 'Sarah'],
],
[
['Anna', 'Sarah', 'Ben'],
['vorname', true],
['Sarah', 'Ben', 'Anna'],
],
[
['Wölflinge', 'Pfadfinder'],
['select', false],
['Pfadfinder', 'Wölflinge'],
]
]);

View File

@ -3,7 +3,6 @@
namespace Tests; namespace Tests;
use App\Form\Models\Form; use App\Form\Models\Form;
use App\Invoice\Models\Invoice;
use App\Member\Member; use App\Member\Member;
use Laravel\Scout\Console\FlushCommand; use Laravel\Scout\Console\FlushCommand;
use Laravel\Scout\Console\SyncIndexSettingsCommand; use Laravel\Scout\Console\SyncIndexSettingsCommand;
@ -27,7 +26,6 @@ abstract class EndToEndTestCase extends TestCase
config()->set('scout.driver', 'meilisearch'); config()->set('scout.driver', 'meilisearch');
Artisan::call(FlushCommand::class, ['model' => Member::class]); Artisan::call(FlushCommand::class, ['model' => Member::class]);
Artisan::call(FlushCommand::class, ['model' => Form::class]); Artisan::call(FlushCommand::class, ['model' => Form::class]);
Artisan::call(FlushCommand::class, ['model' => Invoice::class]);
Artisan::call(SyncIndexSettingsCommand::class); Artisan::call(SyncIndexSettingsCommand::class);
return $this; return $this;

View File

@ -2,202 +2,211 @@
namespace Tests\Feature\Contribution; namespace Tests\Feature\Contribution;
use App\Contribution\Documents\ContributionDocument;
use App\Contribution\Documents\RdpNrwDocument; use App\Contribution\Documents\RdpNrwDocument;
use App\Contribution\Documents\CitySolingenDocument; use App\Contribution\Documents\CitySolingenDocument;
use App\Contribution\Documents\WuppertalDocument;
use App\Country; use App\Country;
use App\Gender; use App\Gender;
use App\Invoice\InvoiceSettings; use App\Invoice\InvoiceSettings;
use App\Member\Member; use App\Member\Member;
use Generator;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Laravel\Passport\Client; use Laravel\Passport\Client;
use Laravel\Passport\Passport; use Laravel\Passport\Passport;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\RequestFactories\ContributionMemberApiRequestFactory; use Tests\RequestFactories\ContributionMemberApiRequestFactory;
use Tests\RequestFactories\ContributionRequestFactory; use Tests\RequestFactories\ContributionRequestFactory;
use Tests\TestCase;
use Zoomyboy\Tex\Tex; use Zoomyboy\Tex\Tex;
uses(DatabaseTransactions::class); class StoreTest extends TestCase
{
use DatabaseTransactions;
dataset('validation', function () { /**
return [ * @testWith ["App\\Contribution\\Documents\\CitySolingenDocument", ["Super tolles Lager", "Max Muster", "Jane Muster", "15.06.1991"]]
[ * ["App\\Contribution\\Documents\\RdpNrwDocument", ["Muster, Max", "Muster, Jane", "15.06.1991", "42777 SG"]]
['type' => 'aaa'], * ["App\\Contribution\\Documents\\CityRemscheidDocument", ["Max", "Muster", "Jane"]]
CitySolingenDocument::class, * ["App\\Contribution\\Documents\\CityFrankfurtMainDocument", ["Max", "Muster", "Jane"]]
'type', * ["App\\Contribution\\Documents\\BdkjHesse", ["Max", "Muster", "Jane"]]
], *
[ * @param array<int, string> $bodyChecks
['type' => ''], */
CitySolingenDocument::class, public function testItCompilesContributionDocumentsViaRequest(string $type, array $bodyChecks): void
'type', {
], $this->withoutExceptionHandling();
[ Tex::spy();
['dateFrom' => ''], $this->login()->loginNami();
CitySolingenDocument::class, $member1 = Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Max', 'lastname' => 'Muster']);
'dateFrom', $member2 = Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Jane', 'lastname' => 'Muster']);
],
[
['dateFrom' => '2022-01'],
CitySolingenDocument::class,
'dateFrom',
],
[
['dateUntil' => ''],
CitySolingenDocument::class,
'dateUntil',
],
[
['dateUntil' => '2022-01'],
CitySolingenDocument::class,
'dateUntil',
],
[
['country' => -1],
RdpNrwDocument::class,
'country',
],
[
['country' => 'AAAA'],
RdpNrwDocument::class,
'country',
],
[
['members' => 'A'],
RdpNrwDocument::class,
'members',
],
[
['members' => [99999]],
RdpNrwDocument::class,
'members.0',
],
[
['members' => ['lalala']],
RdpNrwDocument::class,
'members.0',
],
[
['eventName' => ''],
CitySolingenDocument::class,
'eventName',
],
[
['zipLocation' => ''],
CitySolingenDocument::class,
'zipLocation',
],
[
['zipLocation' => ''],
WuppertalDocument::class,
'zipLocation',
],
[
['dateFrom' => ''],
WuppertalDocument::class,
'dateFrom',
],
[
['dateUntil' => ''],
WuppertalDocument::class,
'dateUntil',
],
];
});
it('compiles documents via api', function (string $type, array $bodyChecks) { $response = $this->call('GET', '/contribution-generate', [
$this->withoutExceptionHandling(); 'payload' => ContributionRequestFactory::new()->type($type)->state([
Tex::spy(); 'dateFrom' => '1991-06-15',
$this->login()->loginNami(); 'dateUntil' => '1991-06-16',
$member1 = Member::factory()->defaults()->male()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Max', 'lastname' => 'Muster']); 'eventName' => 'Super tolles Lager',
$member2 = Member::factory()->defaults()->female()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Jane', 'lastname' => 'Muster']); 'members' => [$member1->id, $member2->id],
'type' => $type,
'zipLocation' => '42777 SG',
])->toBase64(),
]);
$response = $this->call('GET', '/contribution-generate', [ $response->assertSessionDoesntHaveErrors();
'payload' => ContributionRequestFactory::new()->type($type)->state([ $response->assertOk();
Tex::assertCompiled($type, fn ($document) => $document->hasAllContent($bodyChecks));
}
public function testItCompilesGroupNameInSolingenDocument(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
Tex::spy();
InvoiceSettings::fake(['from_long' => 'Stamm BiPi']);
$this->call('GET', '/contribution-generate', [
'payload' => ContributionRequestFactory::new()->type(CitySolingenDocument::class)->toBase64(),
]);
Tex::assertCompiled(CitySolingenDocument::class, fn ($document) => $document->hasAllContent(['Stamm BiPi']));
}
public function testItCompilesContributionDocumentsViaApi(): void
{
$this->withoutExceptionHandling();
Tex::spy();
Gender::factory()->female()->create();
Gender::factory()->male()->create();
Passport::actingAsClient(Client::factory()->create(), ['contribution-generate']);
$country = Country::factory()->create();
Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Max', 'lastname' => 'Muster']);
Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Jane', 'lastname' => 'Muster']);
$response = $this->postJson('/api/contribution-generate', [
'country' => $country->id,
'dateFrom' => '1991-06-15', 'dateFrom' => '1991-06-15',
'dateUntil' => '1991-06-16', 'dateUntil' => '1991-06-16',
'eventName' => 'Super tolles Lager', 'eventName' => 'Super tolles Lager',
'members' => [$member1->id, $member2->id], 'type' => CitySolingenDocument::class,
'type' => $type,
'zipLocation' => '42777 SG', 'zipLocation' => '42777 SG',
])->toBase64(), 'member_data' => [
]); ContributionMemberApiRequestFactory::new()->create(),
ContributionMemberApiRequestFactory::new()->create(),
],
]);
$response->assertSessionDoesntHaveErrors(); $response->assertSessionDoesntHaveErrors();
$response->assertOk(); $response->assertOk();
Tex::assertCompiled($type, fn ($document) => $document->hasAllContent($bodyChecks)); Tex::assertCompiled(CitySolingenDocument::class, fn ($document) => $document->hasAllContent(['Super']));
})->with([ }
["App\\Contribution\\Documents\\CitySolingenDocument", ["Super tolles Lager", "Max Muster", "Jane Muster", "15.06.1991"]],
["App\\Contribution\\Documents\\RdpNrwDocument", ["Muster, Max", "Muster, Jane", "15.06.1991", "42777 SG"]],
["App\\Contribution\\Documents\\CityRemscheidDocument", ["Max", "Muster", "Jane"]],
["App\\Contribution\\Documents\\CityFrankfurtMainDocument", ["Max", "Muster", "Jane"]],
["App\\Contribution\\Documents\\BdkjHesse", ["Max", "Muster", "Jane"]],
["App\\Contribution\\Documents\\WuppertalDocument", ["Max", "Muster", "Jane", "42777 SG", "15.06.1991", "16.06.1991"]],
]);
it('testItCompilesGroupNameInSolingenDocument', function () { /**
$this->withoutExceptionHandling()->login()->loginNami(); * @testWith [""]
Tex::spy(); * ["aaaa"]
InvoiceSettings::fake(['from_long' => 'Stamm BiPi']); * ["YWFhCg=="]
*/
public function testInputShouldBeBase64EncodedJson(string $payload): void
{
$this->login()->loginNami();
$this->call('GET', '/contribution-generate', [ $this->call('GET', '/contribution-generate', ['payload' => $payload])->assertSessionHasErrors('payload');
'payload' => ContributionRequestFactory::new()->type(CitySolingenDocument::class)->toBase64(), }
]);
Tex::assertCompiled(CitySolingenDocument::class, fn ($document) => $document->hasAllContent(['Stamm BiPi'])); /**
}); * @param array<string, string> $input
* @param class-string<ContributionDocument> $documentClass
*/
#[DataProvider('validationDataProvider')]
public function testItValidatesInput(array $input, string $documentClass, string $errorField): void
{
$this->login()->loginNami();
Country::factory()->create();
Member::factory()->defaults()->create();
it('testItCompilesContributionDocumentsViaApi', function () { $this->postJson('/contribution-validate', ContributionRequestFactory::new()->type($documentClass)->state($input)->create())
$this->withoutExceptionHandling(); ->assertJsonValidationErrors($errorField);
Tex::spy(); }
Gender::factory()->female()->create();
Gender::factory()->male()->create();
Passport::actingAsClient(Client::factory()->create(), ['contribution-generate']);
$country = Country::factory()->create();
Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Max', 'lastname' => 'Muster']);
Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Jane', 'lastname' => 'Muster']);
$response = $this->postJson('/api/contribution-generate', [ /**
'country' => $country->id, * @param array<string, string> $input
'dateFrom' => '1991-06-15', * @param class-string<ContributionDocument> $documentClass
'dateUntil' => '1991-06-16', */
'eventName' => 'Super tolles Lager', #[DataProvider('validationDataProvider')]
'type' => CitySolingenDocument::class, public function testItValidatesInputBeforeGeneration(array $input, string $documentClass, string $errorField): void
'zipLocation' => '42777 SG', {
'member_data' => [ $this->login()->loginNami();
ContributionMemberApiRequestFactory::new()->create(), Country::factory()->create();
ContributionMemberApiRequestFactory::new()->create(), Member::factory()->defaults()->create();
],
]);
$response->assertSessionDoesntHaveErrors(); $this->call('GET', '/contribution-generate', [
$response->assertOk(); 'payload' => ContributionRequestFactory::new()->type($documentClass)->state($input)->toBase64(),
Tex::assertCompiled(CitySolingenDocument::class, fn ($document) => $document->hasAllContent(['Super'])); ])->assertSessionHasErrors($errorField);
}); }
it('testInputShouldBeBase64EncodedJson', function (string $payload) { public static function validationDataProvider(): Generator
$this->login()->loginNami(); {
yield [
$this->call('GET', '/contribution-generate', ['payload' => $payload])->assertSessionHasErrors('payload'); ['type' => 'aaa'],
})->with([ CitySolingenDocument::class,
[""], 'type',
["aaaa"], ];
["YWFhCg=="], yield [
]); ['type' => ''],
CitySolingenDocument::class,
it('testItValidatesInput', function (array $input, string $documentClass, string $errorField) { 'type',
$this->login()->loginNami(); ];
Country::factory()->create(); yield [
Member::factory()->defaults()->create(); ['dateFrom' => ''],
CitySolingenDocument::class,
$this->postJson('/contribution-validate', ContributionRequestFactory::new()->type($documentClass)->state($input)->create()) 'dateFrom',
->assertJsonValidationErrors($errorField); ];
})->with('validation'); yield [
['dateFrom' => '2022-01'],
it('testItValidatesInputBeforeGeneration', function (array $input, string $documentClass, string $errorField) { CitySolingenDocument::class,
$this->login()->loginNami(); 'dateFrom',
Country::factory()->create(); ];
Member::factory()->defaults()->create(); yield [
['dateUntil' => ''],
$this->call('GET', '/contribution-generate', [ CitySolingenDocument::class,
'payload' => ContributionRequestFactory::new()->type($documentClass)->state($input)->toBase64(), 'dateUntil',
])->assertSessionHasErrors($errorField); ];
})->with('validation'); yield [
['dateUntil' => '2022-01'],
CitySolingenDocument::class,
'dateUntil',
];
yield [
['country' => -1],
RdpNrwDocument::class,
'country',
];
yield [
['country' => 'AAAA'],
RdpNrwDocument::class,
'country',
];
yield [
['members' => 'A'],
RdpNrwDocument::class,
'members',
];
yield [
['members' => [99999]],
RdpNrwDocument::class,
'members.0',
];
yield [
['members' => ['lalala']],
RdpNrwDocument::class,
'members.0',
];
yield [
['eventName' => ''],
CitySolingenDocument::class,
'eventName',
];
yield [
['zipLocation' => ''],
CitySolingenDocument::class,
'zipLocation',
];
}
}

View File

@ -55,22 +55,6 @@ class FormRegisterActionTest extends FormTestCase
$this->assertEquals('Abraham', $participants->first()->data['spitzname']); $this->assertEquals('Abraham', $participants->first()->data['spitzname']);
} }
public function testItCannotRegisterWhenRegistrationFromReached(): void
{
$this->login()->loginNami();
$form = Form::factory()->registrationFrom(now()->addDay())->create();
$this->register($form, [])->assertJsonValidationErrors(['event' => 'Anmeldung zzt nicht möglich.']);
}
public function testItCannotRegisterWhenRegistrationUntilReached(): void
{
$this->login()->loginNami();
$form = Form::factory()->registrationUntil(now()->subDay())->create();
$this->register($form, [])->assertJsonValidationErrors(['event' => 'Anmeldung zzt nicht möglich.']);
}
public function testItSendsEmailToParticipant(): void public function testItSendsEmailToParticipant(): void
{ {
$this->login()->loginNami()->withoutExceptionHandling(); $this->login()->loginNami()->withoutExceptionHandling();

View File

@ -59,19 +59,6 @@ class FormStoreActionTest extends FormTestCase
$this->assertFrontendCacheCleared(); $this->assertFrontendCacheCleared();
} }
public function testItStoresDefaultSorting(): void
{
Event::fake([Succeeded::class]);
$this->login()->loginNami()->withoutExceptionHandling();
FormRequest::new()->fields([$this->textField()])->fake();
$this->postJson(route('form.store'))->assertOk();
$form = Form::latest()->first();
$this->assertEquals('id', $form->meta['sorting']['by']);
$this->assertFalse(false, $form->meta['sorting']['direction']);
}
public function testRegistrationDatesCanBeNull(): void public function testRegistrationDatesCanBeNull(): void
{ {
$this->login()->loginNami()->withoutExceptionHandling(); $this->login()->loginNami()->withoutExceptionHandling();

View File

@ -5,6 +5,8 @@ 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 Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\RequestFactories\EditorRequestFactory; use Tests\RequestFactories\EditorRequestFactory;
@ -29,20 +31,6 @@ class FormUpdateActionTest extends FormTestCase
$this->assertTrue($form->config->sections->get(0)->fields->get(0)->maxToday); $this->assertTrue($form->config->sections->get(0)->fields->get(0)->maxToday);
} }
public function testItSetsRegistrationDates(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->create();
$payload = FormRequest::new()->registrationFrom('2023-05-04 01:00:00')->registrationUntil('2023-07-07 01:00:00')->create();
$this->patchJson(route('form.update', ['form' => $form]), $payload)->assertOk();
$form = $form->fresh();
$this->assertEquals('2023-05-04 01:00', $form->registration_from->format('Y-m-d H:i'));
$this->assertEquals('2023-07-07 01:00', $form->registration_until->format('Y-m-d H:i'));
}
public function testItSetsTexts(): void public function testItSetsTexts(): void
{ {
$this->login()->loginNami()->withoutExceptionHandling(); $this->login()->loginNami()->withoutExceptionHandling();

View File

@ -21,13 +21,13 @@ class FormUpdateMetaActionTest extends FormTestCase
$this->patchJson(route('form.update-meta', ['form' => $form]), [ $this->patchJson(route('form.update-meta', ['form' => $form]), [
'active_columns' => ['textone'], 'active_columns' => ['textone'],
'sorting' => ['by' => 'textone', 'direction' => false], 'sorting' => ['textone', 'desc'],
])->assertOk() ])->assertOk()
->assertJsonPath('active_columns.0', 'textone') ->assertJsonPath('active_columns.0', 'textone')
->assertJsonPath('sorting.by', 'textone'); ->assertJsonPath('sorting.1', 'desc');
$form = Form::latest()->first(); $form = Form::latest()->first();
$this->assertEquals(['by' => 'textone', 'direction' => false], $form->meta['sorting']); $this->assertEquals(['textone', 'desc'], $form->meta['sorting']);
$this->assertEquals(['textone'], $form->meta['active_columns']); $this->assertEquals(['textone'], $form->meta['active_columns']);
} }
@ -38,11 +38,11 @@ class FormUpdateMetaActionTest extends FormTestCase
$this->patchJson(route('form.update-meta', ['form' => $form]), [ $this->patchJson(route('form.update-meta', ['form' => $form]), [
'active_columns' => ['created_at'], 'active_columns' => ['created_at'],
'sorting' => ['by' => 'textone', 'direction' => false], 'sorting' => ['created_at', 'desc'],
])->assertOk(); ])->assertOk();
$form = Form::latest()->first(); $form = Form::latest()->first();
$this->assertEquals(['by' => 'textone', 'direction' => false], $form->fresh()->meta['sorting']); $this->assertEquals(['created_at', 'desc'], $form->fresh()->meta['sorting']);
$this->assertEquals(['created_at'], $form->fresh()->meta['active_columns']); $this->assertEquals(['created_at'], $form->fresh()->meta['active_columns']);
} }
} }

View File

@ -0,0 +1,225 @@
<?php
namespace Tests\Feature\Form;
use App\Form\Fields\TextField;
use App\Form\Models\Form;
use App\Form\Models\Participant;
use App\Form\Scopes\ParticipantFilterScope;
use App\Group;
use App\Member\Member;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ParticipantIndexActionTest extends FormTestCase
{
use DatabaseTransactions;
public function testItShowsParticipantsAndColumns(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$group = Group::factory()->innerName('Stamm')->create();
$form = Form::factory()
->has(Participant::factory()->state(['member_id' => 55])->data(['vorname' => 'Max', 'select' => ['A', 'B'], 'stufe' => 'Pfadfinder', 'test1' => '', 'test2' => '', 'test3' => '', 'birthday' => '1991-04-20', 'bezirk' => $group->id]))
->fields([
$this->textField('vorname')->name('Vorname'),
$this->checkboxesField('select')->options(['A', 'B', 'C']),
$this->dropdownField('stufe')->options(['Wölfling', 'Jungpfadfinder', 'Pfadfinder']),
$this->textField('test1')->name('Test 1'),
$this->textField('test2')->name('Test 2'),
$this->textField('test3')->name('Test 3'),
$this->dateField('birthday')->name('Geburtsdatum'),
$this->groupField('bezirk')->name('bezirk'),
])
->create();
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertOk()
->assertJsonPath('data.0.id', $form->participants->first()->id)
->assertJsonPath('data.0.vorname', 'Max')
->assertJsonPath('data.0.vorname_display', 'Max')
->assertJsonPath('data.0.stufe', 'Pfadfinder')
->assertJsonPath('data.0.bezirk', $group->id)
->assertJsonPath('data.0.member_id', 55)
->assertJsonPath('data.0.bezirk_display', 'Stamm')
->assertJsonPath('data.0.birthday_display', '20.04.1991')
->assertJsonPath('data.0.birthday', '1991-04-20')
->assertJsonPath('data.0.select', ['A', 'B'])
->assertJsonPath('data.0.select_display', 'A, B')
->assertJsonPath('data.0.links.destroy', route('participant.destroy', ['participant' => $form->participants->first()]))
->assertJsonPath('data.0.links.assign', route('participant.assign', ['participant' => $form->participants->first()]))
->assertJsonPath('data.0.links.fields', route('participant.fields', ['participant' => $form->participants->first()]))
->assertJsonPath('data.0.links.update', route('participant.update', ['participant' => $form->participants->first()]))
->assertJsonPath('meta.columns.0.name', 'Vorname')
->assertJsonPath('meta.columns.0.base_type', class_basename(TextField::class))
->assertJsonPath('meta.columns.0.id', 'vorname')
->assertJsonPath('meta.columns.6.display_attribute', 'birthday_display')
->assertJsonPath('meta.columns.0.display_attribute', 'vorname_display')
->assertJsonPath('meta.form_meta.active_columns', ['vorname', 'select', 'stufe', 'test1'])
->assertJsonPath('meta.has_nami_field', false)
->assertJsonPath('meta.links.update_form_meta', route('form.update-meta', ['form' => $form]))
->assertJsonPath('meta.links.store_participant', route('form.participant.store', ['form' => $form]))
->assertJsonPath('meta.form_meta.sorting', ['vorname', 'asc'])
->assertJsonPath('meta.form_config.sections.0.fields.0.key', 'vorname');
}
public function testItShowsEmptyFilters(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->checkboxField('check')->name('Checked')])->create();
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertOk()
->assertJsonPath('meta.filters.0.name', 'Checked')
->assertJsonPath('meta.filters.0.key', 'check')
->assertJsonPath('meta.filters.0.base_type', 'CheckboxField')
->assertJsonPath('meta.default_filter_value', ParticipantFilterScope::$nan);
$this->callFilter('form.participant.index', ['data' => ['check' => null]], ['form' => $form])->assertHasJsonPath('meta.filter.data.check')->assertJsonPath('meta.filter.data.check', null);
$this->callFilter('form.participant.index', ['data' => ['check' => 'A']], ['form' => $form])->assertJsonPath('meta.filter.data.check', 'A');
$this->callFilter('form.participant.index', ['data' => []], ['form' => $form])->assertJsonPath('meta.filter.data.check', ParticipantFilterScope::$nan);
}
public function testItDisplaysHasNamiField(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->namiField('mitglieder')])->create();
$this->callFilter('form.participant.index', [], ['form' => $form])->assertJsonPath('meta.has_nami_field', true);
}
public function testItFiltersParticipantsByCheckboxValue(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->checkboxField('check')])
->has(Participant::factory()->data(['check' => true])->count(1))
->has(Participant::factory()->data(['check' => false])->count(2))
->create();
$this->callFilter('form.participant.index', ['data' => ['check' => ParticipantFilterScope::$nan]], ['form' => $form])
->assertJsonCount(3, 'data');
$this->callFilter('form.participant.index', ['data' => ['check' => true]], ['form' => $form])
->assertJsonCount(1, 'data');
$this->callFilter('form.participant.index', ['data' => ['check' => false]], ['form' => $form])
->assertJsonCount(2, 'data');
}
public function testItFiltersParticipantsByDropdownValue(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->dropdownField('drop')->options(['A', 'B'])])
->has(Participant::factory()->data(['drop' => null])->count(1))
->has(Participant::factory()->data(['drop' => 'A'])->count(2))
->has(Participant::factory()->data(['drop' => 'B'])->count(4))
->create();
$this->callFilter('form.participant.index', ['data' => ['drop' => ParticipantFilterScope::$nan]], ['form' => $form])
->assertJsonCount(7, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => null]], ['form' => $form])
->assertJsonCount(1, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => 'A']], ['form' => $form])
->assertJsonCount(2, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => 'B']], ['form' => $form])
->assertJsonCount(4, 'data');
}
public function testItFiltersParticipantsByRadioValue(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->radioField('drop')->options(['A', 'B'])])
->has(Participant::factory()->data(['drop' => null])->count(1))
->has(Participant::factory()->data(['drop' => 'A'])->count(2))
->has(Participant::factory()->data(['drop' => 'B'])->count(4))
->create();
$this->callFilter('form.participant.index', ['data' => ['drop' => ParticipantFilterScope::$nan]], ['form' => $form])
->assertJsonCount(7, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => 'A']], ['form' => $form])
->assertJsonCount(2, 'data');
$this->callFilter('form.participant.index', ['data' => ['drop' => 'B']], ['form' => $form])
->assertJsonCount(4, 'data');
}
public function testItPresentsNamiField(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()
->has(Participant::factory()->data(['mitglieder' => [['id' => 393], ['id' => 394]]]))
->has(Participant::factory()->nr(393)->data(['mitglieder' => []]))
->has(Participant::factory()->nr(394)->data(['mitglieder' => []]))
->fields([
$this->namiField('mitglieder'),
])
->create();
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertJsonPath('data.0.mitglieder_display', '393, 394');
}
public function testItShowsRegisteredAtColumnAndAttribute(): void
{
Carbon::setTestNow(Carbon::parse('2023-03-05 06:00:00'));
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()
->has(Participant::factory()->data(['vorname' => 'Max']))
->fields([
$this->textField('vorname')->name('Vorname'),
])
->create();
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertJsonPath('data.0.vorname', 'Max')
->assertJsonPath('data.0.vorname_display', 'Max')
->assertJsonPath('data.0.created_at', '2023-03-05 06:00:00')
->assertJsonPath('data.0.created_at_display', '05.03.2023')
->assertJsonPath('meta.columns.1.name', 'Registriert am')
->assertJsonPath('meta.columns.1.id', 'created_at')
->assertJsonPath('meta.columns.1.display_attribute', 'created_at_display');
}
public function testItShowsOnlyParentParticipantsWhenFilterEnabled(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->create();
$participant = Participant::factory()
->has(Participant::factory()->for($form)->count(2), 'children')
->for($form)
->create();
$this->callFilter('form.participant.index', [], ['form' => $form])->assertJsonCount(3, 'data');
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => -1])->assertJsonCount(1, 'data');
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => $participant->id])->assertJsonCount(2, 'data');
}
public function testItShowsChildrenCount(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->create();
$participant = Participant::factory()
->has(Participant::factory()->for($form)->count(2), 'children')
->for($form)
->create();
Participant::factory()->for($form)->create();
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => -1])
->assertJsonPath('data.0.children_count', 2)
->assertJsonPath('data.1.children_count', 0)
->assertJsonPath('data.0.links.children', route('form.participant.index', ['form' => $form, 'parent' => $participant->id]))
->assertJsonPath('meta.current_page', 1);
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => $participant->id])->assertJsonPath('data.0.children_count', 0);
}
public function testItShowsPreventionState(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$participant = Participant::factory()->data(['vorname' => 'Max'])
->for(Member::factory()->defaults()->state(['efz' => null]))
->for(Form::factory())
->create();
$this->callFilter('form.participant.index', [], ['form' => $participant->form])
->assertJsonPath('data.0.prevention_items.0.letter', 'F')
->assertJsonPath('data.0.prevention_items.0.value', false)
->assertJsonPath('data.0.prevention_items.0.tooltip', 'erweitertes Führungszeugnis nicht vorhanden');
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Tests\Feature\EndToEnd; namespace Tests\Feature\Invoice;
use App\Invoice\BillKind; use App\Invoice\BillKind;
use App\Invoice\Enums\InvoiceStatus; use App\Invoice\Enums\InvoiceStatus;
@ -9,11 +9,8 @@ use App\Invoice\Models\InvoicePosition;
use App\Member\Member; use App\Member\Member;
use App\Payment\Subscription; use App\Payment\Subscription;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\EndToEndTestCase;
use Tests\Feature\Invoice\ReceiverRequestFactory;
uses(DatabaseTransactions::class); uses(DatabaseTransactions::class);
uses(EndToEndTestCase::class);
it('testItDisplaysInvoices', function () { it('testItDisplaysInvoices', function () {
$this->login()->loginNami()->withoutExceptionHandling(); $this->login()->loginNami()->withoutExceptionHandling();
@ -28,7 +25,6 @@ it('testItDisplaysInvoices', function () {
->status(InvoiceStatus::SENT) ->status(InvoiceStatus::SENT)
->create(['usage' => 'Usa', 'mail_email' => 'a@b.de']); ->create(['usage' => 'Usa', 'mail_email' => 'a@b.de']);
sleep(2);
test()->get(route('invoice.index')) test()->get(route('invoice.index'))
->assertInertiaPath('data.data.0.to.name', 'Familie Blabla') ->assertInertiaPath('data.data.0.to.name', 'Familie Blabla')
->assertInertiaPath('data.data.0.id', $invoice->id) ->assertInertiaPath('data.data.0.id', $invoice->id)
@ -82,35 +78,19 @@ it('testValuesCanBeNull', function () {
test()->login()->loginNami()->withoutExceptionHandling(); test()->login()->loginNami()->withoutExceptionHandling();
Invoice::factory()->create(); Invoice::factory()->create();
sleep(2);
test()->get(route('invoice.index')) test()->get(route('invoice.index'))
->assertInertiaPath('data.data.0.sent_at_human', ''); ->assertInertiaPath('data.data.0.sent_at_human', '');
}); });
it('filters for invoice status', function (array $filter, int $count) { it('testItFiltersForInvoiceStatus', function () {
test()->login()->loginNami()->withoutExceptionHandling(); test()->login()->loginNami()->withoutExceptionHandling();
Invoice::factory()->status(InvoiceStatus::NEW)->create(); Invoice::factory()->status(InvoiceStatus::NEW)->create();
Invoice::factory()->status(InvoiceStatus::SENT)->count(2)->create(); Invoice::factory()->status(InvoiceStatus::SENT)->count(2)->create();
Invoice::factory()->status(InvoiceStatus::PAID)->count(3)->create(); Invoice::factory()->status(InvoiceStatus::PAID)->count(3)->create();
sleep(2); test()->callFilter('invoice.index', [])->assertInertiaCount('data.data', 3);
test()->callFilter('invoice.index', $filter)->assertInertiaCount('data.data', $count); test()->callFilter('invoice.index', ['statuses' => []])->assertInertiaCount('data.data', 0);
})->with([ test()->callFilter('invoice.index', ['statuses' => ['Neu']])->assertInertiaCount('data.data', 1);
[[], 3], test()->callFilter('invoice.index', ['statuses' => ['Neu', 'Rechnung beglichen']])->assertInertiaCount('data.data', 4);
[['statuses' => []], 0], test()->callFilter('invoice.index', ['statuses' => ['Neu', 'Rechnung beglichen', 'Rechnung gestellt']])->assertInertiaCount('data.data', 6);
[['statuses' => ['Neu']], 1], });
[['statuses' => ['Neu', 'Rechnung beglichen']], 4],
[['statuses' => ['Neu', 'Rechnung beglichen', 'Rechnung gestellt']], 6],
]);
it('searches invoice usage', function (array $filter, int $count) {
test()->login()->loginNami()->withoutExceptionHandling();
Invoice::factory()->status(InvoiceStatus::NEW)->create(['usage' => 'Kein Zweck']);
Invoice::factory()->status(InvoiceStatus::NEW)->create(['usage' => 'Mitgliedsbeitrag']);
sleep(2);
test()->callFilter('invoice.index', $filter)->assertInertiaCount('data.data', $count);
})->with([
[['search' => 'Mitgliedsbeitrag'], 1],
[['search' => 'Kein'], 1],
]);

View File

@ -32,7 +32,6 @@ class EditTest extends TestCase
$this->assertInertiaHas('Biber', $response, "meta.formSubactivities.{$activity->id}.{$subactivity->id}"); $this->assertInertiaHas('Biber', $response, "meta.formSubactivities.{$activity->id}.{$subactivity->id}");
$this->assertInertiaHas('€ Mitglied', $response, "meta.formActivities.{$activity->id}"); $this->assertInertiaHas('€ Mitglied', $response, "meta.formActivities.{$activity->id}");
$this->assertInertiaHas('Max', $response, 'data.firstname'); $this->assertInertiaHas('Max', $response, 'data.firstname');
$this->assertInertiaHas(false, $response, 'data.keepdata');
$this->assertInertiaHas('Doktor', $response, 'data.salutation'); $this->assertInertiaHas('Doktor', $response, 'data.salutation');
$this->assertInertiaHas('Lorem bla', $response, 'data.comment'); $this->assertInertiaHas('Lorem bla', $response, 'data.comment');
$this->assertInertiaHas('edit', $response, 'mode'); $this->assertInertiaHas('edit', $response, 'mode');

View File

@ -17,55 +17,50 @@ use App\Region;
use App\Subactivity; use App\Subactivity;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Phake; use Phake;
use Tests\TestCase;
use Zoomyboy\LaravelNami\Fakes\MemberFake; use Zoomyboy\LaravelNami\Fakes\MemberFake;
uses(DatabaseTransactions::class); class NamiPutMemberActionTest extends TestCase
{
use DatabaseTransactions;
it('testItPutsAMember', function (array $memberAttributes, array $storedAttributes) { public function testItPutsAMember(): void
Fee::factory()->create(); {
$this->stubIo(PullMemberAction::class, fn ($mock) => $mock); Fee::factory()->create();
$this->stubIo(PullMembershipsAction::class, fn ($mock) => $mock); $this->stubIo(PullMemberAction::class, fn ($mock) => $mock);
$this->withoutExceptionHandling()->login()->loginNami(); $this->stubIo(PullMembershipsAction::class, fn ($mock) => $mock);
$country = Country::factory()->create(); $this->withoutExceptionHandling()->login()->loginNami();
$region = Region::factory()->create(); $country = Country::factory()->create();
$nationality = Nationality::factory()->inNami(565)->create(); $region = Region::factory()->create();
$subscription = Subscription::factory()->forFee()->create(); $nationality = Nationality::factory()->inNami(565)->create();
$group = Group::factory()->inNami(55)->create(); $subscription = Subscription::factory()->forFee()->create();
$confession = Confession::factory()->inNami(567)->create(['is_null' => true]); $group = Group::factory()->inNami(55)->create();
app(MemberFake::class)->stores(55, 993); $confession = Confession::factory()->inNami(567)->create(['is_null' => true]);
$activity = Activity::factory()->hasAttached(Subactivity::factory()->name('Biber')->inNami(55))->name('Leiter')->inNami(6)->create(); app(MemberFake::class)->stores(55, 993);
$subactivity = $activity->subactivities->first(); $activity = Activity::factory()->hasAttached(Subactivity::factory()->name('Biber')->inNami(55))->name('Leiter')->inNami(6)->create();
$subactivity = $activity->subactivities->first();
$member = Member::factory() $member = Member::factory()
->for($country) ->for($country)
->for($subscription) ->for($subscription)
->for($region) ->for($region)
->for($nationality) ->for($nationality)
->for($group) ->for($group)
->emailBillKind() ->emailBillKind()
->create($memberAttributes); ->create(['email_parents' => 'a@b.de']);
NamiPutMemberAction::run($member, $activity, $subactivity); NamiPutMemberAction::run($member, $activity, $subactivity);
app(MemberFake::class)->assertStored(55, [ app(MemberFake::class)->assertStored(55, [
'ersteTaetigkeitId' => 6, 'ersteTaetigkeitId' => 6,
'ersteUntergliederungId' => 55, 'ersteUntergliederungId' => 55,
'konfessionId' => 567, 'konfessionId' => 567,
...$storedAttributes, 'emailVertretungsberechtigter' => 'a@b.de',
]); ]);
$this->assertDatabaseHas('members', [ $this->assertDatabaseHas('members', [
'nami_id' => 993, 'nami_id' => 993,
]); ]);
Phake::verify(app(PullMemberAction::class))->handle(55, 993); Phake::verify(app(PullMemberAction::class))->handle(55, 993);
Phake::verify(app(PullMembershipsAction::class))->handle($member); Phake::verify(app(PullMembershipsAction::class))->handle($member);
})->with([ }
[ }
['email_parents' => 'a@b.de'], ['emailVertretungsberechtigter' => 'a@b.de'],
],
[
['keepdata' => true], ['wiederverwendenFlag' => true],
],
[
['keepdata' => false], ['wiederverwendenFlag' => false],
],
]);

View File

@ -39,18 +39,6 @@ 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 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 public function testItDoesntRememberWhenConditionDoesntMatch(): void
{ {
Mail::fake(); Mail::fake();

View File

@ -11,134 +11,142 @@ use App\Nationality;
use App\Payment\Subscription; use App\Payment\Subscription;
use App\Region; use App\Region;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use Zoomyboy\LaravelNami\Fakes\MemberFake; use Zoomyboy\LaravelNami\Fakes\MemberFake;
uses(DatabaseTransactions::class); class PullMemberActionTest extends TestCase
{
use DatabaseTransactions;
beforeEach(function () { public function setUp(): void
Subscription::factory()->name('test')->forFee(300)->create(); {
Gender::factory()->inNami(303)->create(); parent::setUp();
Country::factory()->inNami(302)->create();
Nationality::factory()->inNami(1054)->create();
});
it('testFetchNormalMember', function () { Subscription::factory()->name('test')->forFee(300)->create();
$this->loginNami(); Gender::factory()->inNami(303)->create();
app(MemberFake::class)->shows(1000, 1001, [ Country::factory()->inNami(302)->create();
'vorname' => '::firstname::', Nationality::factory()->inNami(1054)->create();
'nachname' => '::lastname::', $this->loginNami();
'beitragsartId' => 300, }
'geburtsDatum' => '2014-07-11 00:00:00',
'gruppierungId' => 1000,
'geschlechtId' => 303,
'id' => 1001,
'eintrittsdatum' => '2020-11-17 00:00:00',
'landId' => 302,
'staatsangehoerigkeitId' => 1054,
'zeitschriftenversand' => true,
'strasse' => '::street::',
'plz' => '12346',
'ort' => '::location::',
'version' => 40,
'gruppierung' => 'SG Wald',
'mitgliedsNummer' => 53,
]);
$member = app(PullMemberAction::class)->handle(1000, 1001); public function testFetchNormalMember(): void
{
app(MemberFake::class)->shows(1000, 1001, [
'vorname' => '::firstname::',
'nachname' => '::lastname::',
'beitragsartId' => 300,
'geburtsDatum' => '2014-07-11 00:00:00',
'gruppierungId' => 1000,
'geschlechtId' => 303,
'id' => 1001,
'eintrittsdatum' => '2020-11-17 00:00:00',
'landId' => 302,
'staatsangehoerigkeitId' => 1054,
'zeitschriftenversand' => true,
'strasse' => '::street::',
'plz' => '12346',
'ort' => '::location::',
'version' => 40,
'gruppierung' => 'SG Wald',
'mitgliedsNummer' => 53,
]);
$this->assertDatabaseHas('members', [ $member = app(PullMemberAction::class)->handle(1000, 1001);
'firstname' => '::firstname::',
'lastname' => '::lastname::',
'subscription_id' => Subscription::firstWhere('name', 'test')->id,
'birthday' => '2014-07-11',
'group_id' => Group::nami(1000)->id,
'gender_id' => Gender::nami(303)->id,
'nami_id' => 1001,
'joined_at' => '2020-11-17',
'country_id' => Country::nami(302)->id,
'nationality_id' => Nationality::nami(1054)->id,
'send_newspaper' => 1,
'address' => '::street::',
'zip' => '12346',
'location' => '::location::',
'version' => '40',
'mitgliedsnr' => 53,
]);
$this->assertDatabaseHas('groups', [ $this->assertDatabaseHas('members', [
'name' => 'SG Wald', 'firstname' => '::firstname::',
'nami_id' => 1000, 'lastname' => '::lastname::',
'inner_name' => 'SG Wald', 'subscription_id' => Subscription::firstWhere('name', 'test')->id,
]); 'birthday' => '2014-07-11',
$this->assertEquals(1001, $member->nami_id); 'group_id' => Group::nami(1000)->id,
}); 'gender_id' => Gender::nami(303)->id,
'nami_id' => 1001,
'joined_at' => '2020-11-17',
'country_id' => Country::nami(302)->id,
'nationality_id' => Nationality::nami(1054)->id,
'send_newspaper' => 1,
'address' => '::street::',
'zip' => '12346',
'location' => '::location::',
'version' => '40',
'mitgliedsnr' => 53,
]);
it('testFetchWiederverwendenFlag', function (array $memberAttributes, array $storedAttributes) { $this->assertDatabaseHas('groups', [
$this->loginNami(); 'name' => 'SG Wald',
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]); 'nami_id' => 1000,
app(MemberFake::class)->shows(1000, 1001, $memberAttributes); 'inner_name' => 'SG Wald',
]);
$this->assertEquals(1001, $member->nami_id);
}
app(PullMemberAction::class)->handle(1000, 1001); public function testRegionIdIsSetToNull(): void
{
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]);
app(MemberFake::class)->shows(1000, 1001, [
'regionId' => 999,
]);
$this->assertDatabaseHas('members', $storedAttributes); app(PullMemberAction::class)->handle(1000, 1001);
})->with([
[['wiederverwendenFlag' => false], ['keepdata' => false]],
[['wiederverwendenFlag' => true], ['keepdata' => true]],
[['regionId' => 999], ['region_id' => null]]
]);
it('testItSetsFirstSubscriptionFromFee', function () { $this->assertDatabaseHas('members', [
$this->loginNami(); 'region_id' => null,
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]); ]);
$should = Subscription::factory()->forFee(55)->create(); }
app(MemberFake::class)->shows(1000, 1001, [
'beitragsartId' => 55,
]);
app(PullMemberAction::class)->handle(1000, 1001); public function testItSetsFirstSubscriptionFromFee(): void
{
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]);
$should = Subscription::factory()->forFee(55)->create();
app(MemberFake::class)->shows(1000, 1001, [
'beitragsartId' => 55,
]);
$this->assertDatabaseHas('members', [ app(PullMemberAction::class)->handle(1000, 1001);
'subscription_id' => $should->id,
]);
});
it('testItCreatesSubscriptionOnTheFly', function () { $this->assertDatabaseHas('members', [
$this->loginNami(); 'subscription_id' => $should->id,
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]); ]);
app(MemberFake::class)->shows(1000, 1001, [ }
'beitragsartId' => 55,
'beitragsart' => 'Lala',
]);
app(PullMemberAction::class)->handle(1000, 1001); public function testItCreatesSubscriptionOnTheFly(): void
{
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]);
app(MemberFake::class)->shows(1000, 1001, [
'beitragsartId' => 55,
'beitragsart' => 'Lala',
]);
$fee = Fee::where('nami_id', 55)->firstOrFail(); app(PullMemberAction::class)->handle(1000, 1001);
$subscription = Subscription::where('fee_id', $fee->id)->firstOrFail();
$this->assertDatabaseHas('subscriptions', [
'fee_id' => $fee->id,
'name' => 'Lala',
]);
$this->assertDatabaseHas('subscription_children', [
'name' => 'Lala',
'amount' => 1000,
'parent_id' => $subscription->id,
]);
$this->assertDatabaseHas('members', [
'subscription_id' => $subscription->id,
]);
});
it('testItPullsMemberWithNoSubscription', function () { $fee = Fee::where('nami_id', 55)->firstOrFail();
$this->loginNami(); $subscription = Subscription::where('fee_id', $fee->id)->firstOrFail();
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]); $this->assertDatabaseHas('subscriptions', [
app(MemberFake::class)->shows(1000, 1001, [ 'fee_id' => $fee->id,
'beitragsartId' => null, 'name' => 'Lala',
'beitragsart' => null, ]);
]); $this->assertDatabaseHas('subscription_children', [
'name' => 'Lala',
'amount' => 1000,
'parent_id' => $subscription->id,
]);
$this->assertDatabaseHas('members', [
'subscription_id' => $subscription->id,
]);
}
app(PullMemberAction::class)->handle(1000, 1001); public function testItPullsMemberWithNoSubscription(): void
{
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]);
app(MemberFake::class)->shows(1000, 1001, [
'beitragsartId' => null,
'beitragsart' => null,
]);
$this->assertDatabaseHas('members', [ app(PullMemberAction::class)->handle(1000, 1001);
'subscription_id' => null,
]); $this->assertDatabaseHas('members', [
}); 'subscription_id' => null,
]);
}
}

View File

@ -89,35 +89,6 @@ class StoreTest extends TestCase
]); ]);
} }
public function testItStoresWiederverwendenFlag(): void
{
app(MemberFake::class)->stores(55, 103);
Fee::factory()->create();
$this->withoutExceptionHandling()->login()->loginNami();
$activity = Activity::factory()->inNami(89)->create();
$subactivity = Subactivity::factory()->inNami(90)->create();
$subscription = Subscription::factory()->forFee()->create();
$confesstion = Confession::factory()->create(['is_null' => true]);
PullMemberAction::shouldRun();
PullMembershipsAction::shouldRun();
$this
->from('/member/create')
->post('/member', $this->attributes([
'first_activity_id' => $activity->id,
'first_subactivity_id' => $subactivity->id,
'subscription_id' => $subscription->id,
'keepdata' => true,
]))->assertSessionHasNoErrors();
$this->assertDatabaseHas('members', [
'keepdata' => true,
]);
app(MemberFake::class)->assertStored(55, [
'wiederverwendenFlag' => true,
]);
}
public function testItCanStoreAMemberWithoutNami(): void public function testItCanStoreAMemberWithoutNami(): void
{ {
$this->withoutExceptionHandling()->login()->loginNami(); $this->withoutExceptionHandling()->login()->loginNami();

View File

@ -77,23 +77,6 @@ class UpdateTest extends TestCase
]); ]);
} }
public function testItUpdatesWiederverwendenFlag(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = $this->member();
$this->fakeRequest();
NamiPutMemberAction::allowToRun();
$this->patch("/member/{$member->id}", array_merge($member->getAttributes(), [
'keepdata' => true,
'has_nami' => true,
]));
$this->assertDatabaseHas('members', [
'keepdata' => true,
]);
}
public function testItSetsLocationToNull(): void public function testItSetsLocationToNull(): void
{ {
$this->withoutExceptionHandling()->login()->loginNami(); $this->withoutExceptionHandling()->login()->loginNami();

View File

@ -21,6 +21,7 @@ use Zoomyboy\TableDocument\TestsExcelDocuments;
class TestCase extends BaseTestCase class TestCase extends BaseTestCase
{ {
use CreatesApplication;
use TestsInertia; use TestsInertia;
use MakesHttpCalls; use MakesHttpCalls;
use TestsExcelDocuments; use TestsExcelDocuments;