Compare commits

..

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

77 changed files with 3172 additions and 3296 deletions
.docker
.gitignoreCHANGELOG.mdEnvoy.blade.phpREADME.md
app
bin
composer.jsoncomposer.lock
config
database
docker-compose.prod.ymlpackage-lock.json
packages
phpstan.neonphpunit.xml
resources
js/views
invoice
member
views/tex
contribution
templates/contribution
tests

View File

@ -1,4 +1,4 @@
FROM php:8.3.11-fpm AS php
FROM php:8.3.11-fpm as php
WORKDIR /app
RUN ls /app
RUN apt-get update

View File

@ -1,17 +1,16 @@
FROM composer:2.7.9 AS composer
FROM composer:2.7.9 as composer
WORKDIR /app
COPY . /app
RUN composer install --ignore-platform-reqs --no-dev
RUN php artisan telescope:publish
RUN php artisan horizon:publish
FROM node:20.15.0-slim AS node
FROM node:20.15.0-slim as node
WORKDIR /app
COPY . /app
RUN cd packages/adrema-form && npm ci && npm run build && rm -R node_modules && cd ../../
RUN npm ci && npm run prod && npm run img && rm -R node_modules
RUN npm install && npm run prod && npm run img && rm -R node_modules
FROM nginx:1.21.6-alpine AS nginx
FROM nginx:1.21.6-alpine as nginx
WORKDIR /app
COPY --from=node /app /app
COPY --from=composer /app/public/vendor /app/public/vendor

View File

@ -1,15 +1,14 @@
FROM composer:2.7.9 AS composer
FROM composer:2.7.9 as composer
WORKDIR /app
COPY . /app
RUN composer install --ignore-platform-reqs --no-dev
FROM node:20.15.0-slim AS node
FROM node:20.15.0-slim as node
WORKDIR /app
COPY . /app
RUN cd packages/adrema-form && npm ci && npm run build && rm -R node_modules && cd ../../
RUN npm ci && npm run prod && npm run img && rm -R node_modules
RUN npm install && npm run prod && npm run img && rm -R node_modules
FROM zoomyboy/adrema-base:latest AS php
FROM zoomyboy/adrema-base:latest as php
COPY --chown=www-data:www-data . /app
COPY --chown=www-data:www-data --from=node /app/public /app/public
COPY --chown=www-data:www-data --from=composer /app/vendor /app/vendor

1
.gitignore vendored
View File

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

View File

@ -1,29 +1,5 @@
# Letzte Änderungen
### 1.12.7
- Fix: Synchronisation von allen Mitgliedern bei Mail-Verteilern - nicht nur den ersten 20
### 1.12.6
- Fix: Beiträge von Familienmitgliedern splitten
### 1.12.5
- Fix: Synchronisieren von bestehenden Beiträgen aus NaMi
### 1.12.4
- Filter Mitglieder nach Verhaltenskodex
### 1.12.3
- Volltextsuche für Rechnungen
### 1.12.2
- Zuschussliste Gallier
### 1.12.1
- In Teilnehmer-Liste von Veranstaltungen kann nun sortiert und gefiltert werden.

View File

@ -1,4 +1,4 @@
@servers(['docker' => ['stamm-silva@zoomyboy.de', 'stammgallier@stamm-gallier.de', 'dpsg-lennep@zoomyboy.de', 'dpsgbergischland@zoomyboy.de', 'dpsg-koeln@dpsg-koeln.de']])
@servers(['docker' => ['stammsilva@zoomyboy.de', 'stammgallier@stamm-gallier.de', 'dpsg-lennep@zoomyboy.de', 'dpsgbergischland@zoomyboy.de', 'dpsg-koeln@dpsg-koeln.de']])
@task('deploy', ['on' => 'docker'])
cd $ADREMA_PATH

View File

@ -32,16 +32,16 @@ Außerdem ist AdReMa auch problemlos auf Handys und Tablets bedienbar ("mobiles
## Installation des Produktivsystems
1. Verschieben der Docker-Compose
1. Herunterladen der Beispiel Docker-Compose
```cmd
mv docker-compose.prod.yml docker-compose.yml
curl https://git.zoomyboy.de/silva/adrema/raw/branch/master/docker-compose.prod.yml -o docker-compose.yml
```
2. Anwenden der Beispiel Environmentvariablen-Datei
2. Herunterladen der Beispiel Environmentvariablen-Datei
```cmd
mv .app.env.example .app.env
curl https://git.zoomyboy.de/silva/adrema/raw/branch/master/.app.env.example -o .app.env
```
3. In der `.app.env` notwendige Einstellungen vornehmen:
@ -50,8 +50,7 @@ Außerdem ist AdReMa auch problemlos auf Handys und Tablets bedienbar ("mobiles
- Mail-Server Einstellungen `MAIL_PORT`, `MAIL_HOST`, `MAIL_USERNAME`, `MAIL_PASSWORD` und `MAIL_ENCRYPTION` anpassen
- `MAIL_FROM_NAME`: Der Name, der als Absender von E-Mails gesetzt wird (z.B. `Stamm Bipi Service`)
- `MAIL_FROM_ADDRESS`: Die dazu gehörige E-Mail-Adresse, die natürlich für antworten erreichbar sein sollte (z.B. `vorstand@stamm-bipi.de`)
- `DB_PASSWORD` und `MYSQL_PASSWORD`: Mit dem selben neu erstellten, sicheren Passwort für die Datenbank versehen
- `MEILI_MASTER_KEY` Mit einem neu erstellten, sicheren Passwort versehen
- `DB_PASSWORD` und `MYSQL_PASSWORD`: Mit dem selben sicheren Passwort für die Datenbank versehen
- `USER_EMAIL` und `USER_PASSWORD`: Einstellen des standard Adrema Logins
4. Container zur Gennerierung des App-Key starten
@ -100,22 +99,28 @@ Bei dem Setup wird im Daten-Verzeichniss ein Ordner `./data/setup` angelegt. Hie
git clone https://git.zoomyboy.de/silva/adrema.git
```
2. Kopieren der Beispiel Environmentvariablen-Datei
2. Kopieren der Beispiel Docker-Compose für das Entwickeln und nach Wünschen anpassen
```cmd
cp docker-compose.dev.yml docker-compose.yml
```
3. Kopieren der Beispiel Environmentvariablen-Datei
```cmd
cp .app.env.example .app.env
```
3. Submodule aktuallisieren
4. Submodule aktuallisieren
```cmd
git submodule update --init
```
4. Container erstellen
5. Container erstellen
```cmd
docker compose build
```
5. Mit Schritt 3 und den folgenden der [Installation des Produktivsystems](#installation-des-produktivsystems) fortfahren
6. Mit Schritt 3 und den folgenden der [Installation des Produktivsystems](#installation-des-produktivsystems) fortfahren

View File

@ -22,7 +22,7 @@ class InsertMemberAction
{
$region = Region::firstWhere('nami_id', $member->regionId ?: -1);
$payload = [
return Member::updateOrCreate(['nami_id' => $member->id], [
'firstname' => $member->firstname,
'lastname' => $member->lastname,
'joined_at' => $member->joinedAt,
@ -51,26 +51,7 @@ class InsertMemberAction
'mitgliedsnr' => $member->memberId,
'version' => $member->version,
'keepdata' => $member->keepdata,
];
// Dont update subscription if fee id of existing member's subscription is already the same
if ($existing = Member::nami($member->id)) {
if ($existing->subscription && $existing->subscription->fee->nami_id === $member->feeId) {
$payload['subscription_id'] = $existing->subscription->id;
}
}
return tap(Member::updateOrCreate(['nami_id' => $member->id], $payload), function ($insertedMember) use ($member) {
$insertedMember->bankAccount->update([
'iban' => $member->bankAccount->iban,
'bic' => $member->bankAccount->bic,
'blz' => $member->bankAccount->blz,
'account_number' => $member->bankAccount->accountNumber,
'person' => $member->bankAccount->person,
'bank_name' => $member->bankAccount->bankName,
'nami_id' => $member->bankAccount->id,
]);
});
]);
}
public function getSubscription(NamiMember $member): ?Subscription

View File

@ -34,7 +34,7 @@ class Activity extends Model
}
/**
* @return BelongsToMany<Subactivity, $this>
* @return BelongsToMany<Subactivity>
*/
public function subactivities(): BelongsToMany
{

View File

@ -8,7 +8,7 @@ use App\Contribution\Documents\RdpNrwDocument;
use App\Contribution\Documents\CityRemscheidDocument;
use App\Contribution\Documents\CitySolingenDocument;
use App\Contribution\Documents\CityFrankfurtMainDocument;
use App\Contribution\Documents\WuppertalDocument;
use App\Contribution\Documents\GallierDocument;
use Illuminate\Support\Collection;
use Illuminate\Validation\Rule;
@ -23,7 +23,7 @@ class ContributionFactory
CityRemscheidDocument::class,
CityFrankfurtMainDocument::class,
BdkjHesse::class,
WuppertalDocument::class,
GallierDocument::class,
];
/**

View File

@ -8,7 +8,7 @@ use App\Contribution\Traits\HasPdfBackground;
use App\Country;
use Illuminate\Support\Collection;
class WuppertalDocument extends ContributionDocument
class GallierDocument extends ContributionDocument
{
use HasPdfBackground;
@ -62,7 +62,7 @@ class WuppertalDocument extends ContributionDocument
public static function getName(): string
{
return 'Wuppertal';
return 'Gallier';
}
/**

View File

@ -17,7 +17,7 @@ class CourseMember extends Model
public $guarded = [];
/**
* @return BelongsTo<Course, $this>
* @return BelongsTo<Course, self>
*/
public function course(): BelongsTo
{
@ -25,7 +25,7 @@ class CourseMember extends Model
}
/**
* @return BelongsTo<Member, $this>
* @return BelongsTo<Member, self>
*/
public function member(): BelongsTo
{

View File

@ -129,7 +129,6 @@ class Principal implements PrincipalBackendInterface
*/
public function findByUri($uri, $principalPrefix)
{
return null;
}
/**

View File

@ -15,13 +15,14 @@ class Fee extends Model
use HasFactory;
use HasNamiField;
/** @var array<int, string> */
public $fillable = ['name', 'nami_id'];
/** @var bool */
public $timestamps = false;
/**
* @return HasMany<Subscription, $this>
* @return HasMany<Subscription>
*/
public function subscriptions(): HasMany
{

View File

@ -20,7 +20,7 @@ class UpdateParticipantSearchIndexAction
[
'filterableAttributes' => [...$form->getFields()->filterables()->getKeys(), 'parent-id'],
'searchableAttributes' => $form->getFields()->searchables()->getKeys(),
'sortableAttributes' => [...$form->getFields()->sortables()->getKeys(), 'id', 'created_at'],
'sortableAttributes' => [...$form->getFields()->sortables()->getKeys(), 'id'],
'displayedAttributes' => [...$form->getFields()->filterables()->getKeys(), ...$form->getFields()->searchables()->getKeys(), 'id'],
'pagination' => [
'maxTotalHits' => 1000000,

View File

@ -3,11 +3,11 @@
namespace App\Form\Data;
use App\Fileshare\Data\FileshareResourceData;
use App\Form\Fields\Field;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
use Spatie\LaravelData\Support\EloquentCasts\DataEloquentCast;
#[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)]
@ -16,13 +16,4 @@ class ExportData extends Data
public function __construct(public ?FileshareResourceData $root = null, public ?string $groupBy = null, public ?string $toGroupField = null)
{
}
/**
* @param array<int, mixed> $arguments
* @return DataEloquentCast<self>
*/
public static function castUsing(array $arguments): DataEloquentCast
{
return new DataEloquentCast(static::class, $arguments);
}
}

View File

@ -8,7 +8,6 @@ use Illuminate\Support\Collection;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\WithCast;
use Spatie\LaravelData\Attributes\WithTransformer;
use Spatie\LaravelData\Support\EloquentCasts\DataEloquentCast;
class FormConfigData extends Data
{
@ -30,13 +29,4 @@ class FormConfigData extends Data
new FieldCollection([])
);
}
/**
* @param array<int, mixed> $arguments
* @return DataEloquentCast<self>
*/
public static function castUsing(array $arguments): DataEloquentCast
{
return new DataEloquentCast(static::class, $arguments);
}
}

View File

@ -62,7 +62,7 @@ class Form extends Model implements HasMedia
}
/**
* @return HasMany<Participant, $this>
* @return HasMany<Participant>
*/
public function participants(): HasMany
{

View File

@ -33,7 +33,7 @@ class Participant extends Model implements Preventable
];
/**
* @return BelongsTo<Form, $this>
* @return BelongsTo<Form, self>
*/
public function form(): BelongsTo
{
@ -41,7 +41,7 @@ class Participant extends Model implements Preventable
}
/**
* @return HasMany<Participant, $this>
* @return HasMany<self>
*/
public function children(): HasMany
{
@ -49,7 +49,7 @@ class Participant extends Model implements Preventable
}
/**
* @return BelongsTo<Member, $this>
* @return BelongsTo<Member, self>
*/
public function member(): BelongsTo
{
@ -112,6 +112,6 @@ class Participant extends Model implements Preventable
/** @return array<string, mixed> */
public function toSearchableArray(): array
{
return [...$this->data, 'parent-id' => $this->parent_id, 'created_at' => $this->created_at->timestamp];
return [...$this->data, 'parent-id' => $this->parent_id];
}
}

View File

@ -26,7 +26,7 @@ class Group extends Model
];
/**
* @return BelongsTo<Group, $this>
* @return BelongsTo<self, self>
*/
public function parent(): BelongsTo
{
@ -34,7 +34,7 @@ class Group extends Model
}
/**
* @return HasMany<Group, $this>
* @return HasMany<self>
*/
public function children(): HasMany
{

View File

@ -18,7 +18,5 @@ class Authenticate extends Middleware
if (!$request->expectsJson()) {
return route('login');
}
return null;
}
}

View File

@ -6,9 +6,9 @@ use Lorisleiva\Actions\Concerns\AsAction;
use App\Invoice\Models\Invoice;
use App\Invoice\Resources\InvoiceResource;
use App\Invoice\Scopes\InvoiceFilterScope;
use Illuminate\Pagination\LengthAwarePaginator;
use Inertia\Inertia;
use Inertia\Response;
use Laravel\Scout\Builder;
use Lorisleiva\Actions\ActionRequest;
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
@ -32,7 +32,7 @@ class InvoiceIndexAction
$filter = InvoiceFilterScope::fromRequest($request->input('filter', ''));
return Inertia::render('invoice/Index', [
'data' => InvoiceResource::collection($this->handle($filter)->paginate(15)),
'data' => InvoiceResource::collection($this->handle($filter)),
]);
}
}

View File

@ -39,7 +39,7 @@ class MassStoreAction
foreach ($memberGroup as $members) {
$invoice = Invoice::createForMember($members->first(), $members, $year);
$invoice->save();
$invoice->positions()->createMany($invoice->positions->toArray());
$invoice->positions()->createMany($invoice->positions);
$invoices->push($invoice->fresh('positions'));
}

View File

@ -8,17 +8,27 @@ use Lorisleiva\Actions\ActionRequest;
class InvoiceSettings extends LocalSettings implements Storeable
{
public ?string $from_long;
public ?string $from;
public ?string $mobile;
public ?string $email;
public ?string $website;
public ?string $address;
public ?string $place;
public ?string $zip;
public ?string $iban;
public ?string $bic;
public ?int $rememberWeeks;
public string $from_long;
public string $from;
public string $mobile;
public string $email;
public string $website;
public string $address;
public string $place;
public string $zip;
public string $iban;
public string $bic;
public int $rememberWeeks;
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\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Laravel\Scout\Searchable;
use stdClass;
class Invoice extends Model
{
/** @use HasFactory<InvoiceFactory> */
use HasFactory;
use Searchable;
public $guarded = [];
@ -37,7 +35,7 @@ class Invoice extends Model
];
/**
* @return HasMany<InvoicePosition, $this>
* @return HasMany<InvoicePosition>
*/
public function positions(): HasMany
{
@ -49,6 +47,7 @@ class Invoice extends Model
*/
public static function createForMember(Member $member, Collection $members, int $year, Subscription $subscription = null): self
{
$subscription = $subscription ?: $member->subscription;
$invoice = new self([
'to' => [
'name' => 'Familie ' . $member->lastname,
@ -65,8 +64,7 @@ class Invoice extends Model
$positions = collect([]);
foreach ($members as $member) {
$memberSubscription = $subscription ?: $member->subscription;
foreach ($memberSubscription->children as $child) {
foreach ($subscription->children as $child) {
$positions->push([
'description' => str($child->name)->replace('{name}', $member->firstname . ' ' . $member->lastname)->replace('{year}', (string) $year),
'price' => $child->amount,
@ -122,6 +120,15 @@ class Invoice extends Model
->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
{
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

@ -16,7 +16,7 @@ class InvoicePosition extends Model
public $guarded = [];
/**
* @return BelongsTo<Member, $this>
* @return BelongsTo<Member, self>
*/
public function member(): BelongsTo
{
@ -24,7 +24,7 @@ class InvoicePosition extends Model
}
/**
* @return BelongsTo<Invoice, $this>
* @return BelongsTo<Invoice, self>
*/
public function invoice(): BelongsTo
{

View File

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

View File

@ -6,7 +6,6 @@ use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataCollection;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Support\EloquentCasts\DataEloquentCast;
class Condition extends Data
{
@ -43,13 +42,4 @@ class Condition extends Data
{
return $this->mode === ConditionMode::ALL;
}
/**
* @param array<int, mixed> $arguments
* @return DataEloquentCast<self>
*/
public static function castUsing(array $arguments): DataEloquentCast
{
return new DataEloquentCast(static::class, $arguments);
}
}

View File

@ -3,7 +3,6 @@
namespace App\Lib\Editor;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Support\EloquentCasts\DataEloquentCast;
/** @todo replace blocks with actual block data classes */
class EditorData extends Data implements Editorable
@ -84,13 +83,4 @@ class EditorData extends Data implements Editorable
{
return $this;
}
/**
* @param array<int, mixed> $arguments
* @return DataEloquentCast<self>
*/
public static function castUsing(array $arguments): DataEloquentCast
{
return new DataEloquentCast(static::class, $arguments);
}
}

View File

@ -8,7 +8,7 @@ use Spatie\LaravelData\Data;
/**
* @template T of Model
* @property Builder<T> $query
* @property Builder $query
*/
abstract class Filter extends Data
{
@ -40,7 +40,7 @@ abstract class Filter extends Data
}
/**
* @return static
* @return static(self<T>)
*/
public function toDefault(): self
{

View File

@ -8,7 +8,7 @@ use Spatie\LaravelData\Data;
/**
* @template T of Model
* @property Builder<T> $query
* @property Builder $query
*/
abstract class ScoutFilter extends Data
{

View File

@ -25,7 +25,7 @@ class ResyncAction
*/
public function getResults(Maildispatcher $dispatcher): Collection
{
return FilterScope::fromPost($dispatcher->filter)->noPageLimit()->getQuery()->get()
return FilterScope::fromPost($dispatcher->filter)->getQuery()->get()
->filter(fn ($member) => $member->email || $member->email_parents)
->map(fn ($member) => MailEntry::from(['email' => $member->email ?: $member->email_parents]))
->unique(fn ($member) => $member->email);

View File

@ -23,7 +23,7 @@ class Maildispatcher extends Model
];
/**
* @return BelongsTo<Mailgateway, $this>
* @return BelongsTo<Mailgateway, self>
*/
public function gateway(): BelongsTo
{

View File

@ -68,9 +68,9 @@ abstract class Type
}
/**
* @return mixed
* @return array<string, array<string, mixed>>
*/
public function toResource()
public function toResource(): array
{
return [
'cls' => get_class($this),
@ -93,8 +93,8 @@ abstract class Type
}
$this->list($name, $domain)
->filter(fn ($listEntry) => $results->doesntContain(fn ($r) => $r->is($listEntry)))
->each(fn ($listEntry) => $this->remove($name, $domain, $listEntry->email));
->filter(fn ($listEntry) => $results->doesntContain(fn ($r) => $r->is($listEntry)))
->each(fn ($listEntry) => $this->remove($name, $domain, $listEntry->email));
}
/**

View File

@ -10,7 +10,6 @@ use App\Member\Member;
use App\Setting\NamiSettings;
use App\Subactivity;
use Lorisleiva\Actions\Concerns\AsAction;
use Zoomyboy\LaravelNami\Data\BankAccount;
use Zoomyboy\LaravelNami\Data\Member as NamiMember;
class NamiPutMemberAction
@ -48,16 +47,6 @@ class NamiPutMemberAction
'id' => $member->nami_id,
'version' => $member->version,
'keepdata' => $member->keepdata,
'bankAccount' => BankAccount::from([
'bankName' => $member->bankAccount->bank_name,
'id' => $member->bankAccount->nami_id,
'memberId' => $member->mitgliedsnr,
'iban' => $member->bankAccount->iban,
'bic' => $member->bankAccount->bic,
'blz' => $member->bankAccount->blz,
'person' => $member->bankAccount->person,
'accountNumber' => $member->bankAccount->account_number,
]),
]);
$response = $api->putMember($namiMember, $activity ? $activity->nami_id : null, $subactivity ? $subactivity->nami_id : null);
Member::withoutEvents(function () use ($response, $member) {

View File

@ -1,17 +0,0 @@
<?php
namespace App\Member;
use Database\Factories\Member\BankAccountFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class BankAccount extends Model
{
/** @use HasFactory<BankAccountFactory> */
use HasFactory;
public $guarded = [];
public $primaryKey = 'member_id';
}

View File

@ -9,8 +9,7 @@ use Zoomyboy\LaravelNami\Data\Member as NamiMember;
use Zoomyboy\LaravelNami\Data\MembershipEntry as NamiMembershipEntry;
use Spatie\LaravelData\Attributes\DataCollectionOf;
class FullMember extends Data
{
class FullMember extends Data {
/**
* @param DataCollection<int, NamiCourse> $courses
* @param DataCollection<int, NamiMembershipEntry> $memberships
@ -21,6 +20,6 @@ class FullMember extends Data
public DataCollection $courses,
#[DataCollectionOf(NamiMembershipEntry::class)]
public DataCollection $memberships,
) {
}
) {}
}

View File

@ -46,8 +46,6 @@ class FilterScope extends ScoutFilter
public array $exclude = [],
public ?bool $hasFullAddress = null,
public ?bool $hasBirthday = null,
public ?bool $hasSvk = null,
public ?bool $hasVk = null,
) {
}
@ -87,12 +85,6 @@ class FilterScope extends ScoutFilter
if ($this->hasBirthday === true) {
$filter->push('birthday IS NOT NULL');
}
if ($this->hasSvk !== null) {
$filter->push('has_svk = ' . ($this->hasSvk ? 'true' : 'false'));
}
if ($this->hasVk !== null) {
$filter->push('has_vk = ' . ($this->hasVk ? 'true' : 'false'));
}
if ($this->ausstand === true) {
$filter->push('ausstand > 0');
}

View File

@ -34,7 +34,6 @@ use Zoomyboy\Osm\HasGeolocation;
use Zoomyboy\Phone\HasPhoneNumbers;
use App\Prevention\Enums\Prevention;
use Database\Factories\Member\MemberFactory;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* @property string $subscription_name
@ -196,7 +195,7 @@ class Member extends Model implements Geolocatable
// ---------------------------------- Relations ----------------------------------
/**
* @return BelongsTo<Country, $this>
* @return BelongsTo<Country, self>
*/
public function country(): BelongsTo
{
@ -204,7 +203,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return BelongsTo<Gender, $this>
* @return BelongsTo<Gender, self>
*/
public function gender(): BelongsTo
{
@ -212,7 +211,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return BelongsTo<Region, $this>
* @return BelongsTo<Region, self>
*/
public function region(): BelongsTo
{
@ -223,7 +222,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return HasMany<InvoicePosition, $this>
* @return HasMany<InvoicePosition>
*/
public function invoicePositions(): HasMany
{
@ -231,7 +230,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return BelongsTo<Confession, $this>
* @return BelongsTo<Confession, self>
*/
public function confession(): BelongsTo
{
@ -239,7 +238,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return BelongsTo<Nationality, $this>
* @return BelongsTo<Nationality, self>
*/
public function nationality(): BelongsTo
{
@ -247,7 +246,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return BelongsTo<Subscription, $this>
* @return BelongsTo<Subscription, self>
*/
public function subscription(): BelongsTo
{
@ -255,7 +254,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return BelongsTo<Group, $this>
* @return BelongsTo<Group, self>
*/
public function group(): BelongsTo
{
@ -263,7 +262,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return HasMany<CourseMember, $this>
* @return HasMany<CourseMember>
*/
public function courses(): HasMany
{
@ -271,7 +270,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return HasMany<Membership, $this>
* @return HasMany<Membership>
*/
public function memberships(): HasMany
{
@ -279,7 +278,7 @@ class Member extends Model implements Geolocatable
}
/**
* @return HasMany<Membership, $this>
* @return HasMany<Membership>
*/
public function leaderMemberships(): HasMany
{
@ -287,34 +286,21 @@ class Member extends Model implements Geolocatable
}
/**
* @return HasMany<Membership, $this>
* @return HasMany<Membership>
*/
public function ageGroupMemberships(): HasMany
{
return $this->memberships()->isAgeGroup()->active();
}
/**
* @return HasOne<BankAccount, $this>
*/
public function bankAccount(): HasOne
{
return $this->hasOne(BankAccount::class);
}
public static function booted()
{
static::created(function (self $model): void {
$model->bankAccount()->create([]);
});
static::deleting(function (self $model): void {
$model->memberships->each->delete();
$model->courses->each->delete();
$model->invoicePositions->each(function ($position) {
$position->delete();
});
$model->bankAccount()->delete();
});
}
@ -558,8 +544,6 @@ class Member extends Model implements Geolocatable
'bill_kind' => $this->bill_kind?->value,
'group_id' => $this->group->id,
'group_name' => $this->group->inner_name ?: $this->group->name,
'has_vk' => $this->has_vk,
'has_svk' => $this->has_svk,
'links' => [
'show' => route('member.show', ['member' => $this], false),
'edit' => route('member.edit', ['member' => $this], false),

View File

@ -163,10 +163,6 @@ class MemberResource extends JsonResource
'activity_ids' => [],
'subactivity_ids' => []
],
'boolean_filter' => [
['id' => true, 'name' => 'Ja'],
['id' => false, 'name' => 'Nein'],
],
'default' => [
'gender_id' => null,
'salutation' => '',

View File

@ -32,7 +32,7 @@ class Membership extends Model
];
/**
* @return BelongsTo<Activity, $this>
* @return BelongsTo<Activity, self>
*/
public function activity(): BelongsTo
{
@ -40,7 +40,7 @@ class Membership extends Model
}
/**
* @return BelongsTo<Group, $this>
* @return BelongsTo<Group, self>
*/
public function group(): BelongsTo
{
@ -48,7 +48,7 @@ class Membership extends Model
}
/**
* @return BelongsTo<Subactivity, $this>
* @return BelongsTo<Subactivity, self>
*/
public function subactivity(): BelongsTo
{
@ -56,7 +56,7 @@ class Membership extends Model
}
/**
* @return BelongsTo<Member, $this>
* @return BelongsTo<Member, self>
*/
public function member(): BelongsTo
{

View File

@ -14,6 +14,9 @@ class Subscription extends Model
/** @use HasFactory<SubscriptionFactory> */
use HasFactory;
/**
* @var array<int, string>
*/
public $fillable = ['name', 'fee_id'];
public function getAmount(): int
@ -22,7 +25,7 @@ class Subscription extends Model
}
/**
* @return BelongsTo<Fee, $this>
* @return BelongsTo<Fee, self>
*/
public function fee(): BelongsTo
{
@ -30,7 +33,7 @@ class Subscription extends Model
}
/**
* @return HasMany<SubscriptionChild, $this>
* @return HasMany<SubscriptionChild>
*/
public function children(): HasMany
{

View File

@ -16,6 +16,9 @@ class Subactivity extends Model
use HasNamiField;
use Sluggable;
/**
* @var array<int, string>
*/
public $fillable = ['is_age_group', 'is_filterable', 'slug', 'name', 'nami_id'];
/**
@ -23,6 +26,9 @@ class Subactivity extends Model
*/
public $timestamps = false;
/**
* @var array<string, string>
*/
public $casts = [
'is_age_group' => 'boolean',
'is_filterable' => 'boolean',
@ -41,7 +47,7 @@ class Subactivity extends Model
}
/**
* @return BelongsToMany<Activity, $this>
* @return BelongsToMany<Activity>
*/
public function activities(): BelongsToMany
{

View File

@ -3,8 +3,8 @@
echo "drop database scoutrobot;" | sudo mysql
echo "create database scoutrobot;" | sudo mysql
ssh -l stamm-silva zoomyboy.de "cd /usr/share/webapps/adrema_silva && docker compose exec db mysqldump -udb -p$SCOUTROBOT_DB_PASSWORD db" > db.tmp
sudo mysql adrema < 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
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

View File

@ -80,12 +80,12 @@
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
"larastan/larastan": "^3.0",
"larastan/larastan": "^2.0",
"laravel/envoy": "^2.8",
"mockery/mockery": "^1.4.4",
"orchestra/testbench": "^9.0",
"pestphp/pest": "^3.0",
"phpstan/phpstan-mockery": "^2.0"
"phpstan/phpstan-mockery": "^1.1"
},
"config": {
"optimize-autoloader": true,

4020
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
<?php
return [
'email' => env('USER_EMAIL', 'admin@example.com'),
'password' => env('USER_PASSWORD', 'admin'),
];

View File

@ -1,7 +1,6 @@
<?php
use App\Form\Models\Form;
use App\Invoice\Models\Invoice;
use App\Member\Member;
return [
@ -138,7 +137,7 @@ return [
'key' => env('MEILI_MASTER_KEY', null),
'index-settings' => [
Member::class => [
'filterableAttributes' => ['address', 'birthday', 'ausstand', 'bill_kind', 'group_id', 'memberships', 'has_vk', 'has_svk', 'id'],
'filterableAttributes' => ['address', 'birthday', 'ausstand', 'bill_kind', 'group_id', 'memberships', 'id'],
'searchableAttributes' => ['fullname', 'address'],
'sortableAttributes' => ['lastname', 'firstname'],
'displayedAttributes' => ['age_group_icon', 'group_name', 'links', 'is_leader', 'lastname', 'firstname', 'fullname', 'address', 'ausstand', 'birthday', 'id', 'memberships', 'bill_kind', 'group_id'],
@ -154,16 +153,7 @@ return [
'pagination' => [
'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

@ -1,36 +0,0 @@
<?php
namespace Database\Factories\Member;
use App\Member\BankAccount;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<BankAccount>
*/
class BankAccountFactory extends Factory
{
protected $model = BankAccount::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'bank_name' => $this->faker->name(),
'bic' => $this->faker->swiftBicNumber(),
'iban' => $this->faker->iban('DE'),
'blz' => $this->faker->name(),
'person' => $this->faker->name(),
'account_number' => $this->faker->name(),
];
}
public function inNami(int $namiId): self
{
return $this->state(['nami_id' => $namiId]);
}
}

View File

@ -37,8 +37,6 @@ class MemberFactory extends Factory
'email' => $this->faker->safeEmail(),
'recertified_at' => null,
'keepdata' => false,
'has_svk' => $this->faker->boolean(),
'has_vk' => $this->faker->boolean(),
];
}
@ -93,13 +91,6 @@ class MemberFactory extends Factory
return $this->state(['nami_id' => $namiId]);
}
public function withBankAccount(BankAccountFactory $factory): self
{
return $this->afterCreating(function ($member) use ($factory) {
$member->bankAccount->update($factory->make()->toArray());
});
}
public function sameFamilyAs(Member $member): self
{
return $this->state([

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
{
//
}
};

View File

@ -1,34 +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::create('bank_accounts', function (Blueprint $table) {
$table->unsignedBigInteger('member_id')->primary();
$table->unsignedBigInteger('nami_id')->nullable();
$table->string('iban')->nullable();
$table->string('bic')->nullable();
$table->string('blz')->nullable();
$table->string('bank_name')->nullable();
$table->string('person')->nullable();
$table->string('account_number')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('bank_accounts');
}
};

View File

@ -16,11 +16,10 @@ class UserSeeder extends Seeder
public function run()
{
User::create([
'email' => config('init.email'),
'email' => env('USER_EMAIL', 'admin@example.com'),
'email_verified_at' => now(),
'password' => Hash::make(config('init.password')),
'firstname' => 'Adrema',
'lastname' => 'Benutzer',
'password' => Hash::make(env('USER_PASSWORD', 'admin')),
'name' => 'Adrema Benutzer',
]);
}
}

View File

@ -2,6 +2,7 @@ version: '3'
services:
webservice:
image: zoomyboy/adrema-webservice:latest
restart: always
depends_on:
- php
ports:
@ -11,6 +12,7 @@ services:
php:
image: zoomyboy/adrema-app:latest
restart: always
depends_on:
- db
- redis
@ -36,6 +38,7 @@ services:
horizon:
image: zoomyboy/adrema-app:latest
restart: always
depends_on:
- php
command: /bin/entrypoint horizon
@ -59,6 +62,7 @@ services:
schedule:
image: zoomyboy/adrema-app:latest
restart: always
depends_on:
- php
command: /bin/entrypoint schedule
@ -82,6 +86,7 @@ services:
db:
image: mariadb:10.6.5
restart: always
env_file:
- .app.env
environment:
@ -93,6 +98,7 @@ services:
socketi:
image: quay.io/soketi/soketi:89604f268623cf799573178a7ba56b7491416bde-16-debian
restart: always
environment:
SOKETI_DEFAULT_APP_ID: adremaid
SOKETI_DEFAULT_APP_KEY: adremakey
@ -100,6 +106,7 @@ services:
redis:
image: redis:alpine3.18
restart: always
volumes:
- ./data/redis:/data

1396
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit f905c316ee7913cbf85c386021fbaa28b4b2a158
Subproject commit bc61530e510b3d41048984b7cf20b6d82c4f85fb

@ -1 +1 @@
Subproject commit ed283d97ca7680b3c27b2d75da9937f4f379e321
Subproject commit 74800de149bf2ca250a17263cfaf59e48b76186f

View File

@ -7,9 +7,6 @@ parameters:
stubFiles:
- tests/stub/phpstan/TestResponse.stub
- tests/stub/phpstan/Settings.stub
- tests/stub/phpstan/DataEloquentCast.stub
- tests/stub/phpstan/File.stub
- tests/stub/phpstan/CastsAttributes.stub
paths:
- app
@ -33,6 +30,9 @@ parameters:
ignoreErrors:
-
message: "#but does not specify its types: TData#"
-
message: "#cast\\(\\) has parameter \\$properties#"
-
message: "#^Method App\\\\Activity\\:\\:sluggable\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
@ -453,6 +453,11 @@ parameters:
count: 1
path: app/Form/Fields/NamiField.php
-
message: "#^Method App\\\\Fileshare\\\\ConnectionTypes\\\\ConnectionType\\:\\:types\\(\\) should return Illuminate\\\\Support\\\\Collection\\<int, class\\-string\\<App\\\\Fileshare\\\\ConnectionTypes\\\\ConnectionType\\>\\> but returns Illuminate\\\\Support\\\\Collection\\<int, string\\>\\.$#"
count: 1
path: app/Fileshare/ConnectionTypes/ConnectionType.php
-
message: "#^Call to an undefined method Phake\\\\Proxies\\\\StubberProxy\\:\\:check\\(\\)\\.$#"
count: 1
@ -502,45 +507,3 @@ parameters:
message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\<\\*, \\*, \\*\\>\\:\\:isTrying\\(\\)\\.$#"
count: 1
path: app/Membership/TestersBlock.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\<Illuminate\\Database\\Eloquent\\Model\>\:\:isLeader\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Efz/EfzPendingBlock.php
-
message: '#^Method App\\Fileshare\\ConnectionTypes\\ConnectionType\:\:types\(\) should return Illuminate\\Support\\Collection\<int, class\-string\<App\\Fileshare\\ConnectionTypes\\ConnectionType\>\> but returns Illuminate\\Support\\Collection\<int, non\-falsy\-string\>\.$#'
identifier: return.type
count: 1
path: app/Fileshare/ConnectionTypes/ConnectionType.php
-
message: '#^Unable to resolve the template type TGroupKey in call to method Illuminate\\Support\\Collection\<int,App\\Form\\Models\\Participant\>\:\:groupBy\(\)$#'
identifier: argument.templateType
count: 1
path: app/Form/Actions/ExportSyncAction.php
-
message: '#^Parameter \#1 \$callback of method Illuminate\\Support\\Collection\<\(int\|string\),mixed\>\:\:map\(\) contains unresolvable type\.$#'
identifier: argument.unresolvableType
count: 1
path: app/Mailgateway/Resources/MailgatewayResource.php
-
message: '#^Parameter \#1 \$value of method Illuminate\\Support\\Collection\<\(int\|string\),array\<string, mixed\>\>\:\:prepend\(\) contains unresolvable type\.$#'
identifier: argument.unresolvableType
count: 1
path: app/Mailgateway/Resources/MailgatewayResource.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\<Illuminate\\Database\\Eloquent\\Model\>\:\:isLeader\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Member/PsPendingBlock.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\<TRelatedModel of Illuminate\\Database\\Eloquent\\Model\>\:\:isTrying\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Membership/TestersBlock.php

View File

@ -29,7 +29,6 @@
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
<server name="SCOUT_DRIVER" value="database"/>
</php>
<source>
<include>

View File

@ -74,7 +74,6 @@
</ui-popup>
<page-filter breakpoint="xl" :filterable="false">
<template #buttons>
<f-text id="search" :model-value="getFilter('search')" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text>
<f-multipleselect
id="statuses"
:options="meta.statuses"

View File

@ -48,24 +48,6 @@
size="sm"
@update:model-value="setFilter('ausstand', $event)"
></f-switch>
<f-select
id="has_vk"
name="has_vk"
:model-value="getFilter('has_vk')"
label="Verhaltenskodex unterschrieben"
size="sm"
:options="meta.boolean_filter"
@update:model-value="setFilter('has_vk', $event)"
></f-select>
<f-select
id="has_svk"
name="has_svk"
:model-value="getFilter('has_svk')"
label="SVK unterschrieben"
size="sm"
:options="meta.boolean_filter"
@update:model-value="setFilter('has_svk', $event)"
></f-select>
<f-multipleselect
id="group_ids"
:options="meta.groups"

View File

@ -8,7 +8,7 @@
\pagestyle{empty}
\setlength{\parindent}{0cm}
\backgroundsetup{scale = 1, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {wuppertal.pdf}}}
\backgroundsetup{scale = 1, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {gallier.pdf}}}
\begin{document}
\noindent \sffamily
@ -19,6 +19,8 @@
\node[anchor=base west] at (34mm,29.62mm) {\bfseries{\small{<<<$niceDateUntil>>>}}};
\node[] at (203mm,15.55mm) {\bfseries{\small{<<<$zipLocation>>>}}};
\node[thick, cross out,draw=black,text width=2.4mm, text height=2.4mm, inner sep=0mm] at (17.76mm,47.10mm) {};
@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>>>};

View File

@ -80,23 +80,20 @@ it('testItShowsEmptyFilters', function () {
$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) {
it('sorts by active colums sorting by default', function () {
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([
$this->checkboxField('check'),
$this->checkboxField('vorname'),
])->create();
$form->update(['meta' => ['active_columns' => [], 'sorting' => $sorting]]);
$form->update(['meta' => ['active_columns' => [], 'sorting' => ['by' => 'vorname', 'direction' => true]]]);
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],
]);
->assertJsonPath('meta.filter.sort.by', 'vorname')
->assertJsonPath('meta.filter.sort.direction', true);
});
it('testItDisplaysHasNamiField', function () {

View File

@ -202,30 +202,6 @@ class MemberIndexTest extends EndToEndTestCase
]])->assertInertiaCount('data.data', 1);
}
public function testItFiltersForSvkPrevention(): void
{
Member::factory()->defaults()->create(['has_svk' => true]);
Member::factory()->defaults()->create(['has_svk' => false]);
Member::factory()->defaults()->create(['has_svk' => false]);
sleep(1);
$this->callFilter('member.index', ['has_svk' => true])->assertInertiaCount('data.data', 1);
$this->callFilter('member.index', ['has_svk' => false])->assertInertiaCount('data.data', 2);
$this->callFilter('member.index', ['has_svk' => null])->assertInertiaCount('data.data', 3);
}
public function testItFiltersForVkPrevention(): void
{
Member::factory()->defaults()->create(['has_vk' => true]);
Member::factory()->defaults()->create(['has_vk' => false]);
Member::factory()->defaults()->create(['has_vk' => false]);
sleep(1);
$this->callFilter('member.index', ['has_vk' => true])->assertInertiaCount('data.data', 1);
$this->callFilter('member.index', ['has_vk' => false])->assertInertiaCount('data.data', 2);
$this->callFilter('member.index', ['has_vk' => null])->assertInertiaCount('data.data', 3);
}
public function testGroupOfMembershipsFilterCanBeEmpty(): void
{
$mitglied = Activity::factory()->create();

View File

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

View File

@ -4,7 +4,7 @@ namespace Tests\Feature\Contribution;
use App\Contribution\Documents\RdpNrwDocument;
use App\Contribution\Documents\CitySolingenDocument;
use App\Contribution\Documents\WuppertalDocument;
use App\Contribution\Documents\GallierDocument;
use App\Country;
use App\Gender;
use App\Invoice\InvoiceSettings;
@ -87,17 +87,17 @@ dataset('validation', function () {
],
[
['zipLocation' => ''],
WuppertalDocument::class,
GallierDocument::class,
'zipLocation',
],
[
['dateFrom' => ''],
WuppertalDocument::class,
GallierDocument::class,
'dateFrom',
],
[
['dateUntil' => ''],
WuppertalDocument::class,
GallierDocument::class,
'dateUntil',
],
];
@ -130,7 +130,7 @@ it('compiles documents via api', function (string $type, array $bodyChecks) {
["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"]],
["App\\Contribution\\Documents\\GallierDocument", ["Max", "Muster", "Jane", "42777 SG", "15.06.1991", "16.06.1991"]],
]);
it('testItCompilesGroupNameInSolingenDocument', function () {

View File

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

View File

@ -90,8 +90,8 @@ class MassStoreActionTest extends TestCase
$this->assertDatabaseCount('invoices', 1);
$this->assertDatabaseCount('invoice_positions', 2);
$this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Max Muster', 'price' => 4466]);
$this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Jane Muster', 'price' => 4466]);
$this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Max Muster']);
$this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Jane Muster']);
}
public function testItSeparatesBillKinds(): void
@ -105,21 +105,4 @@ class MassStoreActionTest extends TestCase
$this->assertDatabaseCount('invoices', 2);
$this->assertDatabaseCount('invoice_positions', 2);
}
public function testItSeparatesSubscriptions(): void
{
$member1 = Member::factory()->defaults()->emailBillKind()
->for(Subscription::factory()->forFee()->children([new Child('beitrag1 {name}', 4466)]))
->create(['firstname' => 'Member1', 'lastname' => 'ln']);
$member2 = Member::factory()->defaults()->sameFamilyAs($member1)->emailBillKind()
->for(Subscription::factory()->forFee()->children([new Child('beitrag2 {name}', 4467)]))
->create(['firstname' => 'Member2']);
$this->postJson(route('invoice.mass-store'), ['year' => now()->addYear()->year])->assertOk();
$invoice = Invoice::first();
$this->assertDatabaseCount('invoice_positions', 2);
$this->assertDatabaseHas('invoice_positions', ['invoice_id' => $invoice->id, 'member_id' => $member1->id, 'description' => 'beitrag1 Member1 ln', 'price' => 4466]);
$this->assertDatabaseHas('invoice_positions', ['invoice_id' => $invoice->id, 'member_id' => $member2->id, 'description' => 'beitrag2 Member2 ln', 'price' => 4467]);
}
}

View File

@ -7,11 +7,11 @@ use App\Actions\PullMembershipsAction;
use App\Activity;
use App\Confession;
use App\Country;
use App\Gender;
use App\Fee;
use App\Group;
use App\Member\Actions\NamiPutMemberAction;
use App\Member\BankAccount;
use App\Member\Member;
use App\Nationality;
use App\Payment\Subscription;
use App\Region;
use App\Subactivity;
@ -20,130 +20,52 @@ use Phake;
use Zoomyboy\LaravelNami\Fakes\MemberFake;
uses(DatabaseTransactions::class);
covers(NamiPutMemberAction::class);
beforeEach(function () {
it('testItPutsAMember', function (array $memberAttributes, array $storedAttributes) {
Fee::factory()->create();
$this->stubIo(PullMemberAction::class, fn ($mock) => $mock);
$this->stubIo(PullMembershipsAction::class, fn ($mock) => $mock);
Group::factory()->inNami(55)->create();
$this->withoutExceptionHandling()->login()->loginNami();
$country = Country::factory()->create();
$region = Region::factory()->create();
$nationality = Nationality::factory()->inNami(565)->create();
$subscription = Subscription::factory()->forFee()->create();
$group = Group::factory()->inNami(55)->create();
$confession = Confession::factory()->inNami(567)->create(['is_null' => true]);
app(MemberFake::class)->stores(55, 993);
Confession::factory()->inNami(567)->create(['is_null' => true]);
$activity = Activity::factory()->inNami(6)->create();
Subactivity::factory()->hasAttached($activity)->inNami(55)->create();
});
$activity = Activity::factory()->hasAttached(Subactivity::factory()->name('Biber')->inNami(55))->name('Leiter')->inNami(6)->create();
$subactivity = $activity->subactivities->first();
it('pulls member and memberships befre pushing', function () {
$member = Member::factory()->defaults()->create();
$member = Member::factory()
->for($country)
->for($subscription)
->for($region)
->for($nationality)
->for($group)
->emailBillKind()
->create($memberAttributes);
NamiPutMemberAction::run($member, Activity::first(), Subactivity::first());
NamiPutMemberAction::run($member, $activity, $subactivity);
app(MemberFake::class)->assertStored(55, [
'ersteTaetigkeitId' => 6,
'ersteUntergliederungId' => 55,
'konfessionId' => 567,
...$storedAttributes,
]);
$this->assertDatabaseHas('members', [
'nami_id' => 993,
]);
Phake::verify(app(PullMemberAction::class))->handle(55, 993);
Phake::verify(app(PullMembershipsAction::class))->handle($member);
});
it('sets nami id of member', function () {
$member = Member::factory()->defaults()->create();
NamiPutMemberAction::run($member, Activity::first(), Subactivity::first());
$this->assertDatabaseHas('members', ['nami_id' => 993]);
});
it('stores member attributes', function (array $memberAttributes, array $storedAttributes) {
$member = Member::factory()->defaults()->create($memberAttributes);
NamiPutMemberAction::run($member, Activity::first(), Subactivity::first());
app(MemberFake::class)->assertStored(55, $storedAttributes);
})->with([
[['firstname' => 'Phi'], ['vorname' => 'Phi']],
[['lastname' => 'Phi'], ['nachname' => 'Phi']],
[['nickname' => 'Nick'], ['spitzname' => 'Nick']],
[['email' => 'a@b.de'], ['email' => 'a@b.de']],
[['zip' => '5566'], ['plz' => '5566']],
[['location' => 'SG'], ['ort' => 'SG']],
[['further_address' => 'SG'], ['nameZusatz' => 'SG']],
[['other_country' => 'SG'], ['staatsangehoerigkeitText' => 'SG']],
[['address' => 'Add'], ['strasse' => 'Add']],
[['main_phone' => '+49 212 5566234'], ['telefon1' => '+49 212 5566234']],
[['mobile_phone' => '+49 212 5566234'], ['telefon2' => '+49 212 5566234']],
[['work_phone' => '+49 212 5566234'], ['telefon3' => '+49 212 5566234']],
[['email_parents' => 'a@b.de'], ['emailVertretungsberechtigter' => 'a@b.de']],
[['keepdata' => true], ['wiederverwendenFlag' => true]],
[['keepdata' => false], ['wiederverwendenFlag' => false]],
fn () => [['joined_at' => now()], ['eintrittsdatum' => now()->format('Y-m-d') . ' 00:00:00']],
[['fax' => '555'], ['telefax' => '555']],
[[], ['konfessionId' => 567]],
[[], ['ersteTaetigkeitId' => 6]],
[[], ['ersteUntergliederungId' => 55]],
[
['email_parents' => 'a@b.de'], ['emailVertretungsberechtigter' => 'a@b.de'],
],
[
['keepdata' => true], ['wiederverwendenFlag' => true],
],
[
['keepdata' => false], ['wiederverwendenFlag' => false],
],
]);
it('stores related models', function () {
Subscription::factory()->forFee(3)->create();
$member = Member::factory()->defaults()
->for(Country::factory()->inNami(1)->create())
->for(Region::factory()->inNami(2)->create())
->for(Gender::factory()->inNami(4)->create())
->create();
NamiPutMemberAction::run($member, Activity::first(), Subactivity::first());
app(MemberFake::class)->assertStored(55, ['regionId' => 2, 'landId' => 1, 'beitragsartId' => 3, 'geschlechtId' => 4]);
});
it('stores bank account with empty values', function () {
$member = Member::factory()->defaults()->create(['mitgliedsnr' => 56]);
NamiPutMemberAction::run($member, Activity::first(), Subactivity::first());
app(MemberFake::class)->assertStored(55, ['kontoverbindung' => json_encode([
'id' => '',
'zahlungsKonditionId' => null,
'mitgliedsNummer' => 56,
'institut' => '',
'kontoinhaber' => '',
'kontonummer' => '',
'bankleitzahl' => '',
'iban' => '',
'bic' => ''
])]);
});
it('updates existing member', function () {
app(MemberFake::class)->updatesSuccessfully(55, 103)->shows(55, 103);
$member = Member::factory()->defaults()->inNami(103)->create(['version' => 50]);
NamiPutMemberAction::run($member, Activity::first(), Subactivity::first());
app(MemberFake::class)->assertUpdated(55, 103, ['id' => 103, 'version' => 50]);
});
it('updates bank account with filled values', function () {
app(MemberFake::class)->updatesSuccessfully(55, 103)->shows(55, 103);
$member = Member::factory()->defaults()
->withBankAccount(BankAccount::factory()->inNami(30)->state([
'bank_name' => 'Stadt',
'bic' => 'SOLSDE33',
'iban' => 'DE50',
'blz' => 'ssss',
'person' => 'Pill',
'account_number' => 'ddf',
]))
->inNami(103)
->create(['mitgliedsnr' => 56]);
NamiPutMemberAction::run($member, Activity::first(), Subactivity::first());
app(MemberFake::class)->assertUpdated(55, 103, ['kontoverbindung' => json_encode([
'id' => 30,
'zahlungsKonditionId' => null,
'mitgliedsNummer' => 56,
'institut' => 'Stadt',
'kontoinhaber' => 'Pill',
'kontonummer' => 'ddf',
'bankleitzahl' => 'ssss',
'iban' => 'DE50',
'bic' => 'SOLSDE33'
])]);
});

View File

@ -7,7 +7,6 @@ use App\Country;
use App\Fee;
use App\Gender;
use App\Group;
use App\Member\Member;
use App\Nationality;
use App\Payment\Subscription;
use App\Region;
@ -88,26 +87,6 @@ it('testFetchWiederverwendenFlag', function (array $memberAttributes, array $sto
[['regionId' => 999], ['region_id' => null]]
]);
it('testFetchesKontoverbindung', function (array $memberAttributes, array $storedAttributes) {
$this->loginNami();
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]);
app(MemberFake::class)->shows(1000, 1001, $memberAttributes);
app(PullMemberAction::class)->handle(1000, 1001);
$this->assertDatabaseHas('bank_accounts', [
'member_id' => Member::first()->id,
...$storedAttributes
]);
})->with([
[['kontoverbindung' => ['iban' => '3300', 'bic' => 'SOLSDE']], ['iban' => '3300', 'bic' => 'SOLSDE']],
[['kontoverbindung' => ['id' => 33003]], ['nami_id' => 33003]],
[['kontoverbindung' => ['blz' => 111]], ['blz' => 111]],
[['kontoverbindung' => ['institut' => 'Sparkasse']], ['bank_name' => 'Sparkasse']],
[['kontoverbindung' => ['kontoinhaber' => 'Max']], ['person' => 'Max']],
[['kontoverbindung' => ['kontonummer' => '333']], ['account_number' => '333']],
]);
it('testItSetsFirstSubscriptionFromFee', function () {
$this->loginNami();
Region::factory()->inNami(999)->name('nicht-de')->create(['is_null' => true]);
@ -163,20 +142,3 @@ it('testItPullsMemberWithNoSubscription', function () {
'subscription_id' => null,
]);
});
it('doesnt set first subscription if fee matches', function () {
$this->loginNami();
Subscription::factory()->forFee(55)->create();
$otherSubscription = Subscription::factory()->forFee(55)->create();
$member = Member::factory()->defaults()->inNami(1001)->create(['subscription_id' => $otherSubscription->id]);
app(MemberFake::class)->shows(1000, 1001, [
'beitragsartId' => 55,
]);
app(PullMemberAction::class)->handle(1000, 1001);
$this->assertDatabaseHas('members', [
'subscription_id' => $otherSubscription->id,
'id' => $member->id,
]);
});

View File

@ -149,7 +149,7 @@ class ShowTest extends TestCase
->for(Group::factory())
->for(Nationality::factory()->name('deutsch'))
->for(Subscription::factory()->forFee())
->create(['firstname' => 'Max', 'lastname' => 'Muster', 'has_vk' => false, 'has_svk' => false]);
->create(['firstname' => 'Max', 'lastname' => 'Muster']);
$response = $this->get("/member/{$member->id}");

View File

@ -35,10 +35,10 @@ class InitializeGroupsTest extends TestCase
public function testItSynchsAGroupWithASingleNodeAndNoChildren(): void
{
$parentGroup = Group::from(['id' => 150, 'name' => 'lorem', 'parentId' => null]);
$this->api->method('groups')->willReturnMap([
$this->api->method('groups')->will($this->returnValueMap([
[null, collect([$parentGroup])],
[$parentGroup, collect([])],
]);
]));
$this->api->method('groups')->willReturn(collect([]));
(new InitializeGroups($this->api))->handle();
@ -55,10 +55,10 @@ class InitializeGroupsTest extends TestCase
{
$existingGroup = GroupModel::factory()->create(['nami_id' => 150, 'inner_name' => 'Def']);
$parentGroup = Group::from(['id' => 150, 'name' => 'lorem', 'parentId' => null]);
$this->api->method('groups')->willReturnMap([
$this->api->method('groups')->will($this->returnValueMap([
[null, collect([$parentGroup])],
[$parentGroup, collect([])],
]);
]));
(new InitializeGroups($this->api))->handle();
@ -75,11 +75,11 @@ class InitializeGroupsTest extends TestCase
{
$parentGroup = Group::from(['id' => 150, 'name' => 'lorem', 'parentId' => null]);
$subgroup = Group::from(['id' => 200, 'name' => 'subgroup', 'parentId' => 150]);
$this->api->method('groups')->willReturnMap([
$this->api->method('groups')->will($this->returnValueMap([
[null, collect([$parentGroup])],
[$parentGroup, collect([$subgroup])],
[$subgroup, collect([])],
]);
]));
(new InitializeGroups($this->api))->handle();
@ -94,12 +94,12 @@ class InitializeGroupsTest extends TestCase
$parentGroup = Group::from(['id' => 150, 'name' => 'lorem', 'parentId' => null]);
$subgroup = Group::from(['id' => 200, 'name' => 'subgroup', 'parentId' => 150]);
$subsubgroup = Group::from(['id' => 250, 'name' => 'subsubgroup', 'parentId' => 200]);
$this->api->method('groups')->willReturnMap([
$this->api->method('groups')->will($this->returnValueMap([
[null, collect([$parentGroup])],
[$parentGroup, collect([$subgroup])],
[$subgroup, collect([$subsubgroup])],
[$subsubgroup, collect([])],
]);
]));
(new InitializeGroups($this->api))->handle();
@ -111,11 +111,11 @@ class InitializeGroupsTest extends TestCase
$existingSubgroup = GroupModel::factory()->create(['name' => 'Abc', 'inner_name' => 'Def', 'nami_id' => 200]);
$parentGroup = Group::from(['id' => 150, 'name' => 'root', 'parentId' => null]);
$subgroup = Group::from(['id' => 200, 'name' => 'child', 'parentId' => 150]);
$this->api->method('groups')->willReturnMap([
$this->api->method('groups')->will($this->returnValueMap([
[null, collect([$parentGroup])],
[$parentGroup, collect([$subgroup])],
[$subgroup, collect([])],
]);
]));
(new InitializeGroups($this->api))->handle();

View File

@ -1,35 +0,0 @@
<?php
namespace Illuminate\Contracts\Database\Eloquent;
use Illuminate\Database\Eloquent\Model;
/**
* @template TGet
* @template TSet
* @copy
*/
interface CastsAttributes
{
/**
* Transform the attribute from the underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array<string, mixed> $attributes
* @return TGet|null
*/
public function get(Model $model, string $key, mixed $value, array $attributes);
/**
* Transform the attribute to its underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param TSet|null $value
* @param array<string, mixed> $attributes
* @return mixed
*/
public function set(Model $model, string $key, mixed $value, array $attributes);
}

View File

@ -1,13 +0,0 @@
<?php
namespace Spatie\LaravelData\Support\EloquentCasts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
/**
* @template TDto
* @implements CastsAttributes<TDto, TDto>
*/
class DataEloquentCast implements CastsAttributes
{
}

View File

@ -1,11 +0,0 @@
<?php
namespace Symfony\Component\HttpFoundation\File;
/**
* @copy
*/
class File
{
}