Fixed tests

This commit is contained in:
Philipp Lang 2022-11-07 16:18:11 +01:00
parent b717148058
commit a856063380
51 changed files with 811 additions and 827 deletions

View File

@ -2,7 +2,6 @@
namespace App\Console; namespace App\Console;
use App\Payment\PaymentSendCommand;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -14,7 +13,6 @@ class Kernel extends ConsoleKernel
* @var array * @var array
*/ */
protected $commands = [ protected $commands = [
PaymentSendCommand::class,
]; ];
/** /**

View File

@ -6,10 +6,11 @@ use App\Country;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Member\Member; use App\Member\Member;
use App\Member\MemberResource; use App\Member\MemberResource;
use App\Pdf\PdfGenerator;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
use Zoomyboy\Tex\BaseCompiler;
use Zoomyboy\Tex\Tex;
class ContributionController extends Controller class ContributionController extends Controller
{ {
@ -25,10 +26,11 @@ class ContributionController extends Controller
]); ]);
} }
public function generate(Request $request): PdfGenerator public function generate(Request $request): BaseCompiler
{ {
$data = app($request->query('type')); /** @var class-string<SolingenDocument> */
$type = $request->query('type');
return app(PdfGenerator::class)->setRepository($data)->render(); return Tex::compile($type::fromRequest($request));
} }
} }

View File

@ -4,26 +4,33 @@ namespace App\Contribution;
use App\Country; use App\Country;
use App\Member\Member; use App\Member\Member;
use App\Pdf\EnvType;
use App\Pdf\PdfRepository;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Spatie\LaravelData\Data; use Zoomyboy\Tex\Document;
use Zoomyboy\Tex\Engine;
use Zoomyboy\Tex\Template;
class DvData extends Data implements PdfRepository class DvDocument extends Document
{ {
public function __construct( public function __construct(
public string $dateFrom, public string $dateFrom,
public string $dateUntil, public string $dateUntil,
public string $zipLocation, public string $zipLocation,
public ?Country $country, public ?Country $country,
public array $members, public Collection $members,
public ?string $filename = '', public ?string $filename = '',
public string $type = 'F', public string $type = 'F',
) { ) {
} }
public function dateRange(): string
{
return Carbon::parse($this->dateFrom)->format('d.m.Y')
.' - '
.Carbon::parse($this->dateUntil)->format('d.m.Y');
}
public static function fromRequest(Request $request): self public static function fromRequest(Request $request): self
{ {
return new self( return new self(
@ -31,13 +38,13 @@ class DvData extends Data implements PdfRepository
dateUntil: $request->dateUntil, dateUntil: $request->dateUntil,
zipLocation: $request->zipLocation, zipLocation: $request->zipLocation,
country: Country::findOrFail($request->country), country: Country::findOrFail($request->country),
members: $request->members, members: Member::whereIn('id', $request->members)->orderByRaw('lastname, firstname')->get()->chunk(17),
); );
} }
public function members(): Collection public function countryName(): string
{ {
return Member::whereIn('id', $this->members)->orderByRaw('lastname, firstname')->get(); return $this->country->name;
} }
public function memberShort(Member $member): string public function memberShort(Member $member): string
@ -69,31 +76,19 @@ class DvData extends Data implements PdfRepository
return (string) $member->getAge(); return (string) $member->getAge();
} }
public function countryName(): string public function basename(): string
{
return $this->country->name;
}
public function dateRange(): string
{
return Carbon::parse($this->dateFrom)->format('d.m.Y')
.' - '
.Carbon::parse($this->dateUntil)->format('d.m.Y');
}
public function getFilename(): string
{ {
return 'zuschuesse-dv'; return 'zuschuesse-dv';
} }
public function getView(): string public function view(): string
{ {
return 'tex.zuschuss-dv'; return 'tex.zuschuss-dv';
} }
public function getTemplate(): ?string public function template(): Template
{ {
return 'zuschussdv'; return Template::make('tex.templates.contribution');
} }
public function setFilename(string $filename): static public function setFilename(string $filename): static
@ -103,8 +98,8 @@ class DvData extends Data implements PdfRepository
return $this; return $this;
} }
public function getScript(): EnvType public function getEngine(): Engine
{ {
return EnvType::PDFLATEX; return Engine::PDFLATEX;
} }
} }

View File

@ -3,36 +3,61 @@
namespace App\Contribution; namespace App\Contribution;
use App\Member\Member; use App\Member\Member;
use App\Pdf\EnvType;
use App\Pdf\PdfRepository;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Spatie\LaravelData\Data; use Zoomyboy\Tex\Document;
use Zoomyboy\Tex\Engine;
use Zoomyboy\Tex\Template;
class SolingenData extends Data implements PdfRepository class SolingenDocument extends Document
{ {
public function __construct( final private function __construct(
public string $eventName,
public string $dateFrom, public string $dateFrom,
public string $dateUntil, public string $dateUntil,
public string $zipLocation,
/** @var array<int, int> */
public array $members, public array $members,
public ?string $filename = '', public string $eventName,
public string $type = 'F', public string $type = 'F',
) { ) {
} }
public static function fromRequest(Request $request): self public static function fromRequest(Request $request): static
{ {
return new self( return new static(
eventName: $request->eventName,
dateFrom: $request->dateFrom, dateFrom: $request->dateFrom,
dateUntil: $request->dateUntil, dateUntil: $request->dateUntil,
zipLocation: $request->zipLocation,
members: $request->members, members: $request->members,
eventName: $request->eventName,
); );
} }
/**
* @return Collection<Collection<Member>>
*/
public function memberModels(): Collection
{
return Member::whereIn('id', $this->members)->orderByRaw('lastname, firstname')->get()->chunk(14);
}
public function niceEventFrom(): string
{
return Carbon::parse($this->dateFrom)->format('d.m.Y');
}
public function niceEventUntil(): string
{
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 = '';
@ -48,45 +73,18 @@ class SolingenData extends Data implements PdfRepository
return $firstRow."\n".$secondRow; return $firstRow."\n".$secondRow;
} }
public function members(): Collection public function basename(): string
{
return Member::whereIn('id', $this->members)->orderByRaw('lastname, firstname')->get();
}
public function niceEventFrom(): string
{
return Carbon::parse($this->dateFrom)->format('d.m.Y');
}
public function niceEventTo(): string
{
return Carbon::parse($this->dateUntil)->format('d.m.Y');
}
public function getFilename(): string
{ {
return 'zuschuesse-solingen-'.Str::slug($this->eventName); return 'zuschuesse-solingen-'.Str::slug($this->eventName);
} }
public function getView(): string public function view(): string
{ {
return 'tex.zuschuss-stadt'; return 'tex.zuschuss-stadt';
} }
public function getTemplate(): ?string public function getEngine(): Engine
{ {
return null; return Engine::PDFLATEX;
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getScript(): EnvType
{
return EnvType::PDFLATEX;
} }
} }

View File

@ -6,9 +6,6 @@ use App\Activity;
use App\Course\Models\Course; use App\Course\Models\Course;
use App\Member\Member; use App\Member\Member;
use App\Member\MemberResource; use App\Member\MemberResource;
use App\Payment\ActionFactory;
use App\Payment\Payment;
use App\Payment\PaymentResource;
use App\Payment\Status; use App\Payment\Status;
use App\Payment\Subscription; use App\Payment\Subscription;
use App\Region; use App\Region;
@ -43,34 +40,4 @@ class MemberView
})->pluck('subactivities', 'id'), })->pluck('subactivities', 'id'),
]; ];
} }
public function paymentEdit(Member $member, Payment $payment): MemberResource
{
return $this->additional($member, [
'model' => new PaymentResource($payment),
'links' => [['label' => 'Zurück', 'href' => route('member.payment.index', ['member' => $member])]],
'mode' => 'edit',
]);
}
public function paymentIndex(Member $member): MemberResource
{
return $this->additional($member, [
'model' => null,
'links' => [
['icon' => 'plus', 'href' => route('member.payment.create', ['member' => $member])],
],
'payment_links' => app(ActionFactory::class)->forMember($member),
'mode' => 'index',
]);
}
private function additional(Member $member, array $overwrites = []): MemberResource
{
return (new MemberResource($member->load('payments')))
->additional(array_merge([
'subscriptions' => Subscription::pluck('name', 'id'),
'statuses' => Status::pluck('name', 'id'),
], $overwrites));
}
} }

View File

@ -0,0 +1,49 @@
<?php
namespace App\Letter\Actions;
use App\Letter\DocumentFactory;
use App\Payment\PaymentMail;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Lorisleiva\Actions\Concerns\AsAction;
use Mail;
use Zoomyboy\Tex\Tex;
class LetterSendAction
{
use AsAction;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'payment:send';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sends Bills';
/**
* Execute the console command.
*/
private function handle(): int
{
foreach (app(DocumentFactory::class)->types as $type) {
$letters = app(DocumentFactory::class)->repoCollection($type, 'E-Mail');
foreach ($letters as $letter) {
$letterPath = Storage::path(Tex::compile($letter)->storeIn('/tmp', 'local'));
Mail::to($letter->getRecipient())
->send(new PaymentMail($letter, $letterPath));
app(DocumentFactory::class)->afterSingle($letter);
}
}
return 0;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Letter;
use App\Payment\Payment;
use Illuminate\Database\Eloquent\Relations\HasMany;
class BillDocument extends Letter
{
public function linkLabel(): string
{
return 'Rechnung erstellen';
}
public function getSubject(): string
{
return 'Rechnung';
}
public function view(): string
{
return 'tex.bill';
}
public function sendAllLabel(): string
{
return 'Rechnungen versenden';
}
/**
* Get Descriptions for sendpayment page.
*
* @return array<int, string>
*/
public function getDescription(): array
{
return [
'Diese Funktion erstellt ein PDF mit allen noch nicht versendenden Rechnungen bei den Mitgliedern die Post als Versandweg haben.',
'Die Rechnungen werden automatisch auf "Rechnung gestellt" aktualisiert.',
];
}
public function afterSingle(Payment $payment): void
{
$payment->update(['status_id' => 2]);
}
public function getMailSubject(): string
{
return 'Jahresrechnung';
}
/**
* @param HasMany<Payment> $query
*
* @return HasMany<Payment>
*/
public static function paymentsQuery(HasMany $query): HasMany
{
return $query->whereNeedsBill();
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Bill; namespace App\Letter;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;

View File

@ -0,0 +1,149 @@
<?php
namespace App\Letter;
use App\Member\Member;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
class DocumentFactory
{
/**
* @var array<int, class-string<Letter>>
*/
public array $types = [
BillDocument::class,
RememberDocument::class,
];
/**
* @return Collection<int, Letter>
*/
public function getTypes(): Collection
{
/** @var array<int, Member> */
$emptyMembers = [];
return collect(array_map(fn ($classString) => new $classString(collect($emptyMembers)), $this->types));
}
/**
* @param class-string<Letter> $type
*/
public function fromSingleRequest(string $type, Member $member): ?Letter
{
$members = $this->singleMemberCollection($member, $type);
if ($members->isEmpty()) {
return null;
}
$repo = $this->resolve($type, $members);
$repo->setFilename(Str::slug("{$repo->getSubject()} für {$members->first()->singleName}"));
return $repo;
}
/**
* @param class-string<Letter> $type
*/
public function forAll(string $type, string $billKind): ?Letter
{
$members = $this->toPages($this->allMemberCollection($type, $billKind));
if ($members->isEmpty()) {
return null;
}
return $this->resolve($type, $members)->setFilename('alle-rechnungen');
}
/**
* @param class-string<Letter> $type
*
* @return Collection<int, Letter>
*/
public function repoCollection(string $type, string $billKind): Collection
{
$pages = $this->toPages($this->allMemberCollection($type, $billKind));
return $pages->map(fn ($page) => $this->resolve($type, collect([$page])));
}
public function afterSingle(Letter $repo): void
{
foreach ($repo->allPayments() as $payment) {
$repo->afterSingle($payment);
}
}
/**
* @param class-string<Letter> $type
*/
public function afterAll(string $type, string $billKind): void
{
$members = $this->allMemberCollection($type, $billKind);
$repo = $this->resolve($type, $this->toPages($members));
$this->afterSingle($repo);
}
/**
* @param class-string<Letter> $type
*
* @return Collection<int, Page>
*/
public function singleMemberCollection(Member $member, string $type): Collection
{
$members = Member::where($member->only(['lastname', 'address', 'zip', 'location']))
->with([
'payments' => fn ($query) => $type::paymentsQuery($query)
->orderByRaw('nr, member_id'),
])
->get()
->filter(fn (Member $member) => $member->payments->count() > 0);
return $this->toPages($members);
}
/**
* @param class-string<Letter> $type
*
* @return EloquentCollection<Member>
*/
private function allMemberCollection(string $type, string $billKind): Collection
{
return Member::whereHas('billKind', fn (Builder $q) => $q->where('name', $billKind))
->with([
'payments' => fn ($query) => $type::paymentsQuery($query)
->orderByRaw('nr, member_id'),
])
->get()
->filter(fn (Member $member) => $member->payments->count() > 0);
}
/**
* @param class-string<Letter> $type
* @param Collection<int, Page> $pages
*/
private function resolve(string $type, Collection $pages): Letter
{
return new $type($pages);
}
/**
* @param EloquentCollection<Member> $members
*
* @return Collection<int, Page>
*/
private function toPages(EloquentCollection $members): Collection
{
return $members->groupBy(
fn ($member) => Str::slug(
"{$member->lastname}{$member->address}{$member->zip}{$member->location}",
),
)->map(fn ($page) => new Page($page));
}
}

108
app/Letter/Letter.php Normal file
View File

@ -0,0 +1,108 @@
<?php
namespace App\Letter;
use App\Payment\Payment;
use Carbon\Carbon;
use Exception;
use Generator;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Zoomyboy\Tex\Document;
use Zoomyboy\Tex\Engine;
use Zoomyboy\Tex\Template;
abstract class Letter extends Document
{
abstract public function getSubject(): string;
abstract public function view(): string;
abstract public function linkLabel(): string;
abstract public function sendAllLabel(): string;
/**
* @param HasMany<Payment> $query
*
* @return HasMany<Payment>
*/
abstract public static function paymentsQuery(HasMany $query): HasMany;
/**
* @return array<int, string>
*/
abstract public function getDescription(): array;
abstract public function afterSingle(Payment $payment): void;
/**
* @var Collection<int, Page>
*/
public Collection $pages;
public string $subject;
protected string $filename;
public string $until;
/**
* @param Collection<int, Page> $pages
*/
public function __construct(Collection $pages)
{
$this->pages = $pages;
$this->subject = $this->getSubject();
$this->until = now()->addWeeks(2)->format('d.m.Y');
$this->setFilename(Str::slug("{$this->getSubject()} für {$pages->first()?->familyName}"));
}
public function number(int $number): string
{
return number_format($number / 100, 2, '.', '');
}
public function getUntil(): Carbon
{
return now()->addWeeks(2);
}
public function getEngine(): Engine
{
return Engine::XELATEX;
}
public function basename(): string
{
return $this->filename;
}
public function template(): Template
{
return Template::make('tex.templates.default');
}
public function setFilename(string $filename): self
{
$this->filename = $filename;
return $this;
}
public function getRecipient(): MailRecipient
{
if (!$this->pages->first()?->email) {
throw new Exception('Cannot get Recipient. Mail not set.');
}
return new MailRecipient($this->pages->first()->email, $this->pages->first()->familyName);
}
public function allPayments(): Generator
{
foreach ($this->pages as $page) {
foreach ($page->getPayments() as $payment) {
yield $payment;
}
}
}
}

View File

@ -1,10 +1,10 @@
<?php <?php
namespace App\Bill; namespace App\Letter;
use App\Setting\LocalSettings; use App\Setting\LocalSettings;
class BillSettings extends LocalSettings class LetterSettings extends LocalSettings
{ {
public string $from_long; public string $from_long;

View File

@ -0,0 +1,15 @@
<?php
namespace App\Letter;
class MailRecipient
{
public string $name;
public string $email;
public function __construct(string $email, string $name)
{
$this->email = $email;
$this->name = $name;
}
}

68
app/Letter/Page.php Normal file
View File

@ -0,0 +1,68 @@
<?php
namespace App\Letter;
use App\Member\Member;
use App\Payment\Payment;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as BaseCollection;
class Page
{
/**
* @var Collection<Member>
*/
private Collection $members;
public string $familyName;
public string $singleName;
public string $address;
public string $zip;
public string $location;
public string $usage;
public ?string $email;
/**
* @var array<string, string>
*/
public array $positions;
/**
* @param Collection<Member> $members
*/
public function __construct(Collection $members)
{
$this->members = $members;
$this->familyName = $this->members->first()->lastname;
$this->singleName = $members->first()->lastname;
$this->address = $members->first()->address;
$this->zip = $members->first()->zip;
$this->location = $members->first()->location;
$this->email = $members->first()->email_parents ?: $members->first()->email;
$this->positions = $this->getPositions();
$this->usage = "Mitgliedsbeitrag für {$this->familyName}";
}
/**
* @return array<string, string>
*/
public function getPositions(): array
{
return $this->getPayments()->mapWithKeys(function (Payment $payment) {
$key = "Beitrag {$payment->nr} für {$payment->member->firstname} {$payment->member->lastname} ({$payment->subscription->name})";
return [$key => $this->number($payment->subscription->amount)];
})->toArray();
}
/**
* @return BaseCollection<int, Payment>
*/
public function getPayments(): BaseCollection
{
return $this->members->pluck('payments')->flatten(1);
}
public function number(int $number): string
{
return number_format($number / 100, 2, '.', '');
}
}

View File

@ -1,20 +1,13 @@
<?php <?php
namespace App\Pdf; namespace App\Letter;
use App\Member\Member;
use App\Payment\Payment; use App\Payment\Payment;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class RememberType extends Repository implements LetterRepository class RememberDocument extends Letter
{ {
public string $filename;
public function getPayments(Member $member): Collection
{
return $member->payments()->whereNeedsRemember()->get();
}
public function linkLabel(): string public function linkLabel(): string
{ {
return 'Erinnerung erstellen'; return 'Erinnerung erstellen';
@ -37,21 +30,11 @@ class RememberType extends Repository implements LetterRepository
return $this->filename; return $this->filename;
} }
public function getScript(): EnvType public function view(): string
{
return EnvType::XELATEX;
}
public function getView(): string
{ {
return 'tex.remember'; return 'tex.remember';
} }
public function getTemplate(): ?string
{
return 'default';
}
public function getPositions(Collection $page): array public function getPositions(Collection $page): array
{ {
$memberIds = $page->pluck('id')->toArray(); $memberIds = $page->pluck('id')->toArray();
@ -65,11 +48,6 @@ class RememberType extends Repository implements LetterRepository
})->toArray(); })->toArray();
} }
public function getFamilyName(Collection $page): string
{
return $page->first()->lastname;
}
public function getAddress(Collection $page): string public function getAddress(Collection $page): string
{ {
return $page->first()->address; return $page->first()->address;
@ -92,10 +70,10 @@ class RememberType extends Repository implements LetterRepository
public function getUsage(Collection $page): string public function getUsage(Collection $page): string
{ {
return "Mitgliedsbeitrag für {$this->getFamilyName($page)}"; return "Mitgliedsbeitrag für {$page->familyName}";
} }
public function allLabel(): string public function sendAllLabel(): string
{ {
return 'Erinnerungen versenden'; return 'Erinnerungen versenden';
} }
@ -122,4 +100,14 @@ class RememberType extends Repository implements LetterRepository
{ {
return 'Zahlungserinnerung'; return 'Zahlungserinnerung';
} }
/**
* @param HasMany<Payment> $query
*
* @return HasMany<Payment>
*/
public static function paymentsQuery(HasMany $query): HasMany
{
return $query->whereNeedsRemember();
}
} }

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Bill; namespace App\Letter;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
@ -13,7 +13,7 @@ class SettingIndexAction
/** /**
* @return array<string, string> * @return array<string, string>
*/ */
public function handle(BillSettings $settings): array public function handle(LetterSettings $settings): array
{ {
return [ return [
'from_long' => $settings->from_long, 'from_long' => $settings->from_long,
@ -27,7 +27,7 @@ class SettingIndexAction
]; ];
} }
public function asController(BillSettings $settings): Response public function asController(LetterSettings $settings): Response
{ {
session()->put('menu', 'setting'); session()->put('menu', 'setting');
session()->put('title', 'Rechnungs-Einstellungen'); session()->put('title', 'Rechnungs-Einstellungen');

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Bill; namespace App\Letter;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Lorisleiva\Actions\ActionRequest; use Lorisleiva\Actions\ActionRequest;
@ -15,7 +15,7 @@ class SettingSaveAction
*/ */
public function handle(array $input): void public function handle(array $input): void
{ {
$settings = app(BillSettings::class); $settings = app(LetterSettings::class);
$settings->fill([ $settings->fill([
'from_long' => $input['from_long'] ?? '', 'from_long' => $input['from_long'] ?? '',

View File

@ -3,11 +3,11 @@
namespace App\Member; namespace App\Member;
use App\Activity; use App\Activity;
use App\Bill\BillKind;
use App\Confession; use App\Confession;
use App\Country; use App\Country;
use App\Course\Models\CourseMember; use App\Course\Models\CourseMember;
use App\Group; use App\Group;
use App\Letter\BillKind;
use App\Nationality; use App\Nationality;
use App\Payment\Payment; use App\Payment\Payment;
use App\Payment\Subscription; use App\Payment\Subscription;

View File

@ -3,12 +3,12 @@
namespace App\Member; namespace App\Member;
use App\Activity; use App\Activity;
use App\Bill\BillKind;
use App\Confession; use App\Confession;
use App\Country; use App\Country;
use App\Gender; use App\Gender;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Views\MemberView; use App\Http\Views\MemberView;
use App\Letter\BillKind;
use App\Nationality; use App\Nationality;
use App\Payment\Subscription; use App\Payment\Subscription;
use App\Region; use App\Region;

View File

@ -2,31 +2,19 @@
namespace App\Payment; namespace App\Payment;
use App\Member\Member; use App\Letter\DocumentFactory;
use App\Pdf\PdfRepository; use App\Letter\Letter;
use App\Pdf\PdfRepositoryFactory;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class ActionFactory class ActionFactory
{ {
public function forMember(Member $member): Collection
{
return app(PdfRepositoryFactory::class)->getTypes()->map(function (PdfRepository $repo) use ($member): array {
return [
'href' => route('member.singlepdf', ['member' => $member, 'type' => get_class($repo)]),
'label' => $repo->linkLabel(),
'disabled' => !$repo->createable($member),
];
});
}
public function allLinks(): Collection public function allLinks(): Collection
{ {
return app(PdfRepositoryFactory::class)->getTypes()->map(function (PdfRepository $repo) { return app(DocumentFactory::class)->getTypes()->map(function (Letter $repo) {
return [ return [
'link' => [ 'link' => [
'href' => route('sendpayment.pdf', ['type' => get_class($repo)]), 'href' => route('sendpayment.pdf', ['type' => get_class($repo)]),
'label' => $repo->allLabel(), 'label' => $repo->sendAllLabel(),
], ],
'text' => $repo->getDescription(), 'text' => $repo->getDescription(),
]; ];

View File

@ -3,25 +3,12 @@
namespace App\Payment; namespace App\Payment;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Views\MemberView;
use App\Member\Member; use App\Member\Member;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Inertia\Response;
class PaymentController extends Controller class PaymentController extends Controller
{ {
public function index(Request $request, Member $member): Response
{
session()->put('menu', 'member');
session()->put('title', "Zahlungen für Mitglied {$member->fullname}");
$payload = app(MemberView::class)->index($request, []);
$payload['single'] = app(MemberView::class)->paymentIndex($member);
return \Inertia::render('member/VIndex', $payload);
}
public function store(Request $request, Member $member): RedirectResponse public function store(Request $request, Member $member): RedirectResponse
{ {
$member->createPayment($request->validate([ $member->createPayment($request->validate([
@ -33,17 +20,6 @@ class PaymentController extends Controller
return redirect()->back(); return redirect()->back();
} }
public function edit(Member $member, Request $request, Payment $payment): Response
{
session()->put('menu', 'member');
session()->put('title', "Zahlungen für Mitglied {$member->fullname}");
$payload = app(MemberView::class)->index($request, []);
$payload['single'] = app(MemberView::class)->paymentEdit($member, $payment);
return \Inertia::render('member/VIndex', $payload);
}
public function update(Request $request, Member $member, Payment $payment): RedirectResponse public function update(Request $request, Member $member, Payment $payment): RedirectResponse
{ {
$payment->update($request->validate([ $payment->update($request->validate([

View File

@ -2,7 +2,7 @@
namespace App\Payment; namespace App\Payment;
use App\Pdf\LetterRepository; use App\Letter\Letter;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
@ -13,18 +13,20 @@ class PaymentMail extends Mailable
use Queueable; use Queueable;
use SerializesModels; use SerializesModels;
public LetterRepository $repo; public Letter $letter;
public string $filename; public string $filename;
public string $salutation;
/** /**
* Create a new message instance. * Create a new message instance.
* *
* @return void * @return void
*/ */
public function __construct(LetterRepository $repo, string $filename) public function __construct(Letter $letter, string $filename)
{ {
$this->letter = $letter;
$this->filename = $filename; $this->filename = $filename;
$this->repo = $repo; $this->salutation = 'Liebe Familie '.$letter->pages->first()->familyName;
} }
/** /**
@ -34,11 +36,11 @@ class PaymentMail extends Mailable
*/ */
public function build() public function build()
{ {
$template = Str::snake(class_basename($this->repo)); $template = Str::snake(class_basename($this->letter));
return $this->markdown('mail.payment.'.$template) return $this->markdown('mail.payment.'.$template)
->attach($this->filename) ->attach($this->filename)
->replyTo('kasse@stamm-silva.de') ->replyTo('kasse@stamm-silva.de')
->subject($this->repo->getMailSubject().' | DPSG Stamm Silva'); ->subject($this->letter->getSubject().' | DPSG Stamm Silva');
} }
} }

View File

@ -1,80 +0,0 @@
<?php
namespace App\Payment;
use App\Pdf\BillType;
use App\Pdf\PdfGenerator;
use App\Pdf\PdfRepositoryFactory;
use App\Pdf\RememberType;
use Illuminate\Console\Command;
use Mail;
class PaymentSendCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'payment:send';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sends Bills';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->sendBills();
$this->sendRemembers();
return 0;
}
private function sendBills(): void
{
$repos = app(PdfRepositoryFactory::class)->repoCollection(BillType::class, 'E-Mail');
foreach ($repos as $repo) {
$generator = app(PdfGenerator::class)->setRepository($repo)->render();
$to = (object) [
'email' => $repo->getEmail($repo->pages->first()),
'name' => $repo->getFamilyName($repo->pages->first()),
];
Mail::to($to)->send(new PaymentMail($repo, $generator->getCompiledFilename()));
app(PdfRepositoryFactory::class)->afterSingle($repo);
}
}
private function sendRemembers(): void
{
$repos = app(PdfRepositoryFactory::class)->repoCollection(RememberType::class, 'E-Mail');
foreach ($repos as $repo) {
$generator = app(PdfGenerator::class)->setRepository($repo)->render();
$to = (object) [
'email' => $repo->getEmail($repo->pages->first()),
'name' => $repo->getFamilyName($repo->pages->first()),
];
Mail::to($to)->send(new PaymentMail($repo, $generator->getCompiledFilename()));
app(PdfRepositoryFactory::class)->afterSingle($repo);
}
}
}

View File

@ -3,13 +3,13 @@
namespace App\Payment; namespace App\Payment;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Pdf\PdfGenerator; use App\Letter\DocumentFactory;
use App\Pdf\PdfRepositoryFactory;
use Illuminate\Contracts\Support\Responsable; use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response as InertiaResponse; use Inertia\Response as InertiaResponse;
use Zoomyboy\Tex\Tex;
class SendpaymentController extends Controller class SendpaymentController extends Controller
{ {
@ -28,13 +28,15 @@ class SendpaymentController extends Controller
*/ */
public function send(Request $request) public function send(Request $request)
{ {
$repo = app(PdfRepositoryFactory::class)->forAll($request->type, 'Post'); $repo = app(DocumentFactory::class)->forAll($request->type, 'Post');
$pdfFile = app(PdfGenerator::class)->setRepository($repo)->render(); if (is_null($repo)) {
app(PdfRepositoryFactory::class)->afterAll($request->type, 'Post'); return response()->noContent();
}
return null === $repo $pdfFile = Tex::compile($repo);
? response()->noContent() app(DocumentFactory::class)->afterAll($request->type, 'Post');
: $pdfFile;
return $pdfFile;
} }
} }

View File

@ -1,125 +0,0 @@
<?php
namespace App\Pdf;
use App\Member\Member;
use App\Payment\Payment;
use Illuminate\Support\Collection;
class BillType extends Repository implements LetterRepository
{
public string $filename;
public function getPayments(Member $member): Collection
{
return $member->payments()->whereNeedsBill()->get();
}
public function linkLabel(): string
{
return 'Rechnung erstellen';
}
public function getSubject(): string
{
return 'Rechnung';
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getScript(): EnvType
{
return EnvType::XELATEX;
}
public function getFilename(): string
{
return $this->filename;
}
public function getView(): string
{
return 'tex.bill';
}
public function getTemplate(): ?string
{
return 'default';
}
public function getPositions(Collection $page): array
{
$memberIds = $page->pluck('id')->toArray();
$payments = Payment::whereIn('member_id', $memberIds)
->orderByRaw('nr, member_id')->whereNeedsBill()->get();
return $payments->mapWithKeys(function (Payment $payment) {
$key = "Beitrag {$payment->nr} für {$payment->member->firstname} {$payment->member->lastname} ({$payment->subscription->name})";
return [$key => $this->number($payment->subscription->amount)];
})->toArray();
}
public function getFamilyName(Collection $page): string
{
return $page->first()->lastname;
}
public function getAddress(Collection $page): string
{
return $page->first()->address;
}
public function getZip(Collection $page): string
{
return $page->first()->zip;
}
public function getEmail(Collection $page): string
{
return $page->first()->email_parents ?: $page->first()->email;
}
public function getLocation(Collection $page): string
{
return $page->first()->location;
}
public function getUsage(Collection $page): string
{
return "Mitgliedsbeitrag für {$this->getFamilyName($page)}";
}
public function allLabel(): string
{
return 'Rechnungen versenden';
}
/**
* Get Descriptions for sendpayment page.
*
* @return array<int, string>
*/
public function getDescription(): array
{
return [
'Diese Funktion erstellt ein PDF mit allen noch nicht versendenden Rechnungen bei den Mitgliedern die Post als Versandweg haben.',
'Die Rechnungen werden automatisch auf "Rechnung gestellt" aktualisiert.',
];
}
public function afterSingle(Payment $payment): void
{
$payment->update(['status_id' => 2]);
}
public function getMailSubject(): string
{
return 'Jahresrechnung';
}
}

View File

@ -1,68 +0,0 @@
<?php
namespace App\Pdf\Data;
use App\Member\Member;
use App\Pdf\EnvType;
use App\Pdf\PdfRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Spatie\LaravelData\Data;
class MemberEfzData extends Data implements PdfRepository
{
public function __construct(
public ?string $name,
public ?string $secondLine,
public ?string $currentDate,
public ?array $sender = [],
public ?string $filename = '',
) {
}
public static function fromRequest(Request $request): self
{
$memberId = $request->member;
$member = Member::findOrFail($memberId);
return new self(
name: $member->fullname,
secondLine: "geb. am {$member->birthday->format('d.m.Y')}, wohnhaft in {$member->location}",
currentDate: now()->format('d.m.Y'),
sender: [
$member->fullname,
$member->address,
$member->zip.' '.$member->location,
'Mglnr.: '.$member->nami_id,
]
);
}
public function getFilename(): string
{
return 'efz-fuer-'.Str::slug($this->name);
}
public function getView(): string
{
return 'tex.efz';
}
public function getTemplate(): ?string
{
return 'efz';
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getScript(): EnvType
{
return EnvType::PDFLATEX;
}
}

View File

@ -1,9 +0,0 @@
<?php
namespace App\Pdf;
enum EnvType: string
{
case XELATEX = 'XELATEX';
case PDFLATEX = 'PDFLATEX';
}

View File

@ -1,46 +0,0 @@
<?php
namespace App\Pdf;
use App\Member\Member;
use App\Payment\Payment;
use Carbon\Carbon;
use Generator;
use Illuminate\Support\Collection;
interface LetterRepository extends PdfRepository
{
public function getSubject(): string;
public function getPositions(Collection $page): array;
public function getFamilyName(Collection $page): string;
public function getAddress(Collection $page): string;
public function getZip(Collection $page): string;
public function getLocation(Collection $page): string;
public function createable(Member $member): bool;
public function getPayments(Member $member): Collection;
public function linkLabel(): string;
public function getUntil(): Carbon;
public function getUsage(Collection $page): string;
public function allLabel(): string;
public function getEmail(Collection $page): string;
public function getDescription(): array;
public function afterSingle(Payment $payment): void;
public function getMailSubject(): string;
public function allPayments(): Generator;
}

View File

@ -3,10 +3,12 @@
namespace App\Pdf; namespace App\Pdf;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Letter\DocumentFactory;
use App\Member\Member; use App\Member\Member;
use Illuminate\Contracts\Support\Responsable; use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Zoomyboy\Tex\Tex;
class MemberPdfController extends Controller class MemberPdfController extends Controller
{ {
@ -15,10 +17,10 @@ class MemberPdfController extends Controller
*/ */
public function __invoke(Request $request, Member $member) public function __invoke(Request $request, Member $member)
{ {
$repo = app(PdfRepositoryFactory::class)->fromSingleRequest($request->type, $member); $document = app(DocumentFactory::class)->fromSingleRequest($request->type, $member);
return null === $repo return null === $document
? response()->noContent() ? response()->noContent()
: app(PdfGenerator::class)->setRepository($repo)->render(); : Tex::compile($document);
} }
} }

View File

@ -1,67 +0,0 @@
<?php
namespace App\Pdf;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Support\Str;
use Storage;
class PdfGenerator implements Responsable
{
private ?string $filename = null;
private PdfRepository $repo;
private string $dir;
public function setRepository(PdfRepository $repo): self
{
$this->repo = $repo;
return $this;
}
public function render(): self
{
$this->filename = $this->repo->getFilename();
$this->dir = Str::random(32);
Storage::disk('temp')->put($this->dir.'/'.$this->repo->getFilename().'.tex', $this->compileView());
Storage::disk('temp')->makeDirectory($this->dir);
if ($this->repo->getTemplate()) {
$this->copyTemplateTo(Storage::disk('temp')->path($this->dir));
}
$command = 'cd '.Storage::disk('temp')->path($this->dir);
$command .= ' && '.env($this->repo->getScript()->value).' --halt-on-error '.$this->repo->getFilename().'.tex';
$command .= ' && '.env($this->repo->getScript()->value).' --halt-on-error '.$this->repo->getFilename().'.tex';
exec($command, $output, $returnVar);
return $this;
}
public function compileView(): string
{
return view()->make($this->repo->getView(), [
'data' => $this->repo,
])->render();
}
public function toResponse($request)
{
return response()->file($this->getCompiledFilename(), [
'Content-Type' => 'application/pdf',
'Content-Disposition' => "inline; filename=\"{$this->filename}.pdf\"",
]);
}
public function getCompiledFilename(): string
{
return Storage::disk('temp')->path($this->dir.'/'.$this->filename.'.pdf');
}
private function copyTemplateTo(string $destination): void
{
$templatePath = resource_path("views/tex/templates/{$this->repo->getTemplate()}");
exec('cp '.$templatePath.'/* '.$destination);
}
}

View File

@ -1,16 +0,0 @@
<?php
namespace App\Pdf;
interface PdfRepository
{
public function setFilename(string $filename): static;
public function getFilename(): string;
public function getView(): string;
public function getTemplate(): ?string;
public function getScript(): EnvType;
}

View File

@ -1,110 +0,0 @@
<?php
namespace App\Pdf;
use App\Member\Member;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
class PdfRepositoryFactory
{
/**
* @var array<int, class-string<PdfRepository>>
*/
private array $types = [
BillType::class,
RememberType::class,
];
/**
* @return Collection<int, PdfRepository>
*/
public function getTypes(): Collection
{
return collect(array_map(fn ($classString) => new $classString(collect()), $this->types));
}
public function fromSingleRequest(string $type, Member $member): ?LetterRepository
{
$members = $this->singleMemberCollection($member, $type);
if ($members->isEmpty()) {
return null;
}
$repo = $this->resolve($type, $members);
$firstMember = $members->first()->first();
return $repo->setFilename(
Str::slug("{$repo->getSubject()} für {$firstMember->lastname}"),
);
}
public function forAll(string $type, string $billKind): ?PdfRepository
{
$members = $this->toMemberGroup($this->allMemberCollection($type, $billKind));
if ($members->isEmpty()) {
return null;
}
return $this->resolve($type, $members)->setFilename('alle-rechnungen');
}
public function repoCollection(string $type, string $billKind): Collection
{
$members = $this->toMemberGroup($this->allMemberCollection($type, $billKind));
return $members->map(function (Collection $members) use ($type) {
$repo = $this->resolve($type, collect([$members]));
return $repo->setFilename(Str::slug("{$repo->getSubject()} für {$members->first()->lastname}"));
});
}
public function afterSingle(LetterRepository $repo): void
{
foreach ($repo->allPayments() as $payment) {
$repo->afterSingle($payment);
}
}
public function afterAll(string $type, string $billKind): void
{
$members = $this->allMemberCollection($type, $billKind);
$repo = $this->resolve($type, $this->toMemberGroup($members));
$this->afterSingle($repo);
}
public function singleMemberCollection(Member $member, string $type): Collection
{
$members = Member::where($member->only(['lastname', 'address', 'zip', 'location']))
->get()
->filter(fn (Member $member) => app($type)->createable($member));
return $this->toMemberGroup($members);
}
private function allMemberCollection(string $type, string $billKind): Collection
{
return Member::whereHas('billKind', fn (Builder $q) => $q->where('name', $billKind))
->get()
->filter(fn (Member $member) => app($type)->createable($member));
}
private function resolve(string $kind, Collection $members): LetterRepository
{
return new $kind($members);
}
private function toMemberGroup(Collection $members): Collection
{
return $members->groupBy(
fn ($member) => Str::slug(
"{$member->lastname}{$member->address}{$member->zip}{$member->location}",
),
);
}
}

View File

@ -1,46 +0,0 @@
<?php
namespace App\Pdf;
use App\Member\Member;
use Carbon\Carbon;
use Generator;
use Illuminate\Support\Collection;
abstract class Repository
{
abstract public function getPayments(Member $member): Collection;
public Collection $pages;
public function __construct(Collection $pages)
{
$this->pages = $pages;
}
public function number(int $number): string
{
return number_format($number / 100, 2, '.', '');
}
public function getUntil(): Carbon
{
return now()->addWeeks(2);
}
public function createable(Member $member): bool
{
return 0 !== $this->getPayments($member)->count();
}
public function allPayments(): Generator
{
foreach ($this->pages as $page) {
foreach ($page as $member) {
foreach ($this->getPayments($member) as $payment) {
yield $payment;
}
}
}
}
}

View File

@ -2,7 +2,7 @@
namespace App\Setting; namespace App\Setting;
use App\Bill\BillSettings; use App\Letter\LetterSettings;
use App\Mailman\MailmanSettings; use App\Mailman\MailmanSettings;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@ -25,7 +25,7 @@ class SettingServiceProvider extends ServiceProvider
*/ */
public function boot() public function boot()
{ {
app(SettingFactory::class)->register(BillSettings::class); app(SettingFactory::class)->register(LetterSettings::class);
app(SettingFactory::class)->register(MailmanSettings::class); app(SettingFactory::class)->register(MailmanSettings::class);
} }
} }

View File

@ -1,8 +1,8 @@
<?php <?php
namespace Database\Factories\Bill; namespace Database\Factories\Letter;
use App\Bill\BillKind; use App\Letter\BillKind;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**

View File

@ -5,6 +5,7 @@ namespace Database\Factories\Member;
use App\Country; use App\Country;
use App\Fee; use App\Fee;
use App\Group; use App\Group;
use App\Letter\BillKind;
use App\Member\Member; use App\Member\Member;
use App\Nationality; use App\Nationality;
use App\Payment\Payment; use App\Payment\Payment;
@ -60,6 +61,20 @@ class MemberFactory extends Factory
->for($subscription); ->for($subscription);
} }
public function postBillKind(): self
{
return $this->state([
'bill_kind_id' => BillKind::firstWhere('name', 'Post')->id,
]);
}
public function emailBillKind(): self
{
return $this->state([
'bill_kind_id' => BillKind::firstWhere('name', 'E-Mail')->id,
]);
}
public function inNami(int $namiId): self public function inNami(int $namiId): self
{ {
return $this->state(['nami_id' => $namiId]); return $this->state(['nami_id' => $namiId]);

View File

@ -1,6 +1,6 @@
<?php <?php
use App\Bill\BillKind; use App\Letter\BillKind;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;

@ -1 +1 @@
Subproject commit fb9d7a660c6e170eedf7a11b02f7cfeff14346ab Subproject commit 2ffab167ea1628c3b24724c3506b90e5f7120103

View File

@ -1,5 +1,5 @@
@component('mail::message') @component('mail::message')
# Liebe Familie {{ $repo->getFamilyName($repo->pages->first()) }}, # {{ $salutation }},
Im Anhang findet ihr die aktuelle Rechnung des Stammes Silva für das laufende Jahr. Bitte begleicht diese bis zum angegebenen Datum. Im Anhang findet ihr die aktuelle Rechnung des Stammes Silva für das laufende Jahr. Bitte begleicht diese bis zum angegebenen Datum.

View File

@ -1,5 +1,5 @@
@component('mail::message') @component('mail::message')
# Liebe Familie {{ $repo->getFamilyName($repo->pages->first()) }}, # {{ $salutation }},
Hiermit möchten wir euch an die noch ausstehenden Mitgliedsbeiträge des Stammes Silva für das laufende Jahr erinnern. Bitte begleicht diese bis zum angegebenen Datum. Hiermit möchten wir euch an die noch ausstehenden Mitgliedsbeiträge des Stammes Silva für das laufende Jahr erinnern. Bitte begleicht diese bis zum angegebenen Datum.

View File

@ -1,19 +1,19 @@
\documentclass[silvaletter,12pt]{scrlttr2} \documentclass[silvaletter,12pt]{scrlttr2}
\setkomavar{subject}{<<< $data->getSubject() >>>} \setkomavar{subject}{<<< $subject >>>}
\begin{document} \begin{document}
@foreach($data->pages as $page) @foreach($pages as $page)
\begin{letter}{Familie <<< $data->getFamilyName($page) >>>\\<<< $data->getAddress($page) >>>\\<<< $data->getZip($page) >>> <<< $data->getLocation($page) >>>} \begin{letter}{Familie <<< $page->familyName >>>\\<<< $page->address >>>\\<<< $page->zip >>> <<< $page->location >>>}
\sffamily \sffamily
\gdef\TotalHT{0} \gdef\TotalHT{0}
\opening{Liebe Familie <<< $data->getFamilyName($page) >>>,} \opening{Liebe Familie <<< $page->familyName >>>,}
Hiermit stellen wir Ihnen den aktuellen Mitgliedsbeitrag für den \usekomavar*{fromname} und die DPSG in Rechnung. Dieser setzt sich wie folgt zusammen: Hiermit stellen wir Ihnen den aktuellen Mitgliedsbeitrag für den \usekomavar*{fromname} und die DPSG in Rechnung. Dieser setzt sich wie folgt zusammen:
\begin{center} \begin{center}
\begin{tabular}{@{}p{0.5\textwidth}|r} \begin{tabular}{@{}p{0.5\textwidth}|r}
@foreach($data->getPositions($page) as $desc => $price) @foreach($page->positions as $desc => $price)
\product{<<< $desc >>>}{<<< $price >>>} \product{<<< $desc >>>}{<<< $price >>>}
@endforeach @endforeach
\hline \hline
@ -21,13 +21,13 @@
\end{tabular} \end{tabular}
\end{center} \end{center}
Somit bitten wir Sie, den ausstehenden Betrag von \totalttc bis zum \textbf{<<< $data->getUntil()->format('d.m.Y') >>>} auf folgendes Konto zu überweisen: Somit bitten wir Sie, den ausstehenden Betrag von \totalttc bis zum \textbf{<<< $until >>>} auf folgendes Konto zu überweisen:
\begin{tabular}{ll} \begin{tabular}{ll}
Kontoinhaber: & DPSG Stamm Silva \\ Kontoinhaber: & DPSG Stamm Silva \\
IBAN: & DE40 3425 0000 0000 2145 51 \\ IBAN: & DE40 3425 0000 0000 2145 51 \\
Bic: & SOLSDE33XXX \\ Bic: & SOLSDE33XXX \\
Verwendungszweck: & <<<$data->getUsage($page)>>> Verwendungszweck: & <<<$page->usage>>>
\end{tabular} \end{tabular}
Bitte nehmen Sie zur Kenntnis, dass der für jedes Mitglied obligatorische Versicherungsschutz über die DPSG nur dann für Ihr Kind / Ihre Kinder gilt, wenn der Mitgliedsbeitrag bezahlt wurde. Wenn dies nicht geschieht, müssen wir Ihr Kind / Ihre Kinder von allen Pfadfinderaktionen ausschließen. Dazu gehören sowohl die Gruppenstunden sowie Tagesaktionen als auch mehrtägige Lager. Bitte nehmen Sie zur Kenntnis, dass der für jedes Mitglied obligatorische Versicherungsschutz über die DPSG nur dann für Ihr Kind / Ihre Kinder gilt, wenn der Mitgliedsbeitrag bezahlt wurde. Wenn dies nicht geschieht, müssen wir Ihr Kind / Ihre Kinder von allen Pfadfinderaktionen ausschließen. Dazu gehören sowohl die Gruppenstunden sowie Tagesaktionen als auch mehrtägige Lager.

View File

@ -1,19 +1,19 @@
\documentclass[silvaletter,12pt]{scrlttr2} \documentclass[silvaletter,12pt]{scrlttr2}
\setkomavar{subject}{<<< $data->getSubject() >>>} \setkomavar{subject}{<<< $subject >>>}
\begin{document} \begin{document}
@foreach($data->pages as $page) @foreach($pages as $page)
\begin{letter}{Familie <<< $data->getFamilyName($page) >>>\\<<< $data->getAddress($page) >>>\\<<< $data->getZip($page) >>> <<< $data->getLocation($page) >>>} \begin{letter}{Familie <<< $page->familyName >>>\\<<< $page->address >>>\\<<< $page->zip >>> <<< $page->location >>>}
\sffamily \sffamily
\gdef\TotalHT{0} \gdef\TotalHT{0}
\opening{Liebe Familie <<< $data->getFamilyName($page) >>>,} \opening{Liebe Familie <<< $page->familyName >>>,}
Ihr Mitgliedbeitrag ist noch ausstehend. Dieser setzt sich wie folgt zusammen: Ihr Mitgliedbeitrag ist noch ausstehend. Dieser setzt sich wie folgt zusammen:
\begin{center} \begin{center}
\begin{tabular}{@{}p{0.5\textwidth}|r} \begin{tabular}{@{}p{0.5\textwidth}|r}
@foreach($data->getPositions($page) as $desc => $price) @foreach($page->positions as $desc => $price)
\product{<<< $desc >>>}{<<< $price >>>} \product{<<< $desc >>>}{<<< $price >>>}
@endforeach @endforeach
\hline \hline
@ -21,13 +21,13 @@
\end{tabular} \end{tabular}
\end{center} \end{center}
Somit bitten wir Sie, den ausstehenden Betrag von \totalttc bis zum \textbf{<<< $data->getUntil()->format('d.m.Y') >>>} auf folgendes Konto zu überweisen: Somit bitten wir Sie, den ausstehenden Betrag von \totalttc bis zum \textbf{<<< $until >>>} auf folgendes Konto zu überweisen:
\begin{tabular}{ll} \begin{tabular}{ll}
Kontoinhaber: & DPSG Stamm Silva \\ Kontoinhaber: & DPSG Stamm Silva \\
IBAN: & DE40 3425 0000 0000 2145 51 \\ IBAN: & DE40 3425 0000 0000 2145 51 \\
Bic: & SOLSDE33XXX \\ Bic: & SOLSDE33XXX \\
Verwendungszweck: & <<<$data->getUsage($page)>>> Verwendungszweck: & <<<$page->usage>>>
\end{tabular} \end{tabular}
Bitte nehmen Sie zur Kenntnis, dass der für jedes Mitglied obligatorische Versicherungsschutz über die DPSG nur dann für Ihr Kind / Ihre Kinder gilt, wenn der Mitgliedsbeitrag bezahlt wurde. Wenn dies nicht geschieht, müssen wir Ihr Kind / Ihre Kinder von allen Pfadfinderaktionen ausschließen. Dazu gehören sowohl die Gruppenstunden sowie Tagesaktionen als auch mehrtägige Lager. Bitte nehmen Sie zur Kenntnis, dass der für jedes Mitglied obligatorische Versicherungsschutz über die DPSG nur dann für Ihr Kind / Ihre Kinder gilt, wenn der Mitgliedsbeitrag bezahlt wurde. Wenn dies nicht geschieht, müssen wir Ihr Kind / Ihre Kinder von allen Pfadfinderaktionen ausschließen. Dazu gehören sowohl die Gruppenstunden sowie Tagesaktionen als auch mehrtägige Lager.

View File

@ -13,21 +13,21 @@
\begin{document} \begin{document}
\noindent \sffamily \noindent \sffamily
@foreach($data->members()->chunk(17) as $chunk) @foreach($members as $chunk)
\begin{tikzpicture}[remember picture,overlay,yscale=-1] \begin{tikzpicture}[remember picture,overlay,yscale=-1]
\node[anchor=base west] at (38mm,41.62mm) {\bfseries{\large{<<<!!$data->dateRange()!!>>>}}}; \node[anchor=base west] at (38mm,41.62mm) {\bfseries{\large{<<<!!$dateRange!!>>>}}};
\node[anchor=base west] at (135.2mm,41.62mm) {\bfseries{\large{<<<!!$data->zipLocation!!>>>}}}; \node[anchor=base west] at (135.2mm,41.62mm) {\bfseries{\large{<<<!!$zipLocation!!>>>}}};
\node[anchor=base west] at (242.7mm,41.62mm) {\bfseries{\large{<<<!!$data->countryName()!!>>>}}}; \node[anchor=base west] at (242.7mm,41.62mm) {\bfseries{\large{<<<!!$countryName!!>>>}}};
\node[thick, cross out,draw=black,text width=2.4mm, text height=2.4mm, inner sep=0mm] at (17.76mm,47.10mm) {}; \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) @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>>>)$) {<<<$data->memberShort($member)>>>}; \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>>>)$) {<<<$data->memberName($member)>>>}; \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>>>)$) {<<<$data->memberAddress($member)>>>}; \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>>>)$) {<<<$data->memberGender($member)>>>}; \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>>>)$) {<<<$data->memberAge($member)>>>}; \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

@ -83,11 +83,11 @@
\newcommand{\emptycheckbox}{\tikz{\node[text height=0.5cm,text width=0.5cm,inner sep=0cm] at (0,0) {};}} \newcommand{\emptycheckbox}{\tikz{\node[text height=0.5cm,text width=0.5cm,inner sep=0cm] at (0,0) {};}}
\begin{document} \sffamily \begin{document} \sffamily
@foreach($data->members()->chunk(14) as $chunk) @foreach($memberModels as $chunk)
\begin{tikzpicture}[outer] \begin{tikzpicture}[outer]
\path (current page.north west) ++(1cm,-1cm) coordinate (OL) -- (current page.north east) ++(-1cm,0cm) coordinate (OR) node[midway,below=0.5cm] {\textbf{TEILNEHMER - / INNENLISTE}}; \path (current page.north west) ++(1cm,-1cm) coordinate (OL) -- (current page.north east) ++(-1cm,0cm) coordinate (OR) node[midway,below=0.5cm] {\textbf{TEILNEHMER - / INNENLISTE}};
\matrix (options) at ($(OL)+(0.5cm,-1cm)$) [matrix of nodes, column sep=0cm,row sep=0.5cm,nodes in empty cells, every node/.style={inner sep=0cm,align=left,text width=6.2cm}, anchor=north west] { \matrix (options) at ($(OL)+(0.5cm,-1cm)$) [matrix of nodes, column sep=0cm,row sep=0.5cm,nodes in empty cells, every node/.style={inner sep=0cm,align=left,text width=6.2cm}, anchor=north west] {
<<<!!$data->checkboxes()!!>>> <<<!!$checkboxes!!>>>
}; };
\node[align=left,inner sep=0cm,anchor=west] at (options-2-4.west) {\tikz{\node[draw,very thick,rectangle,text height=0.5cm,text width=0.5cm,inner sep=0cm] (checkbox) at (0,0) {}; \draw[thick] (checkbox.south east) ++(0.2cm,0) -- (checkbox.south east -| options-2-4.south east);}}; \node[align=left,inner sep=0cm,anchor=west] at (options-2-4.west) {\tikz{\node[draw,very thick,rectangle,text height=0.5cm,text width=0.5cm,inner sep=0cm] (checkbox) at (0,0) {}; \draw[thick] (checkbox.south east) ++(0.2cm,0) -- (checkbox.south east -| options-2-4.south east);}};
@ -95,13 +95,13 @@
\draw (org.south east -| options-2-2.south west) -- (org.south east -| options-2-4.south east) node[formfill] {DPSG Stamm Silva Solingen Wald}; \draw (org.south east -| options-2-2.south west) -- (org.south east -| options-2-4.south east) node[formfill] {DPSG Stamm Silva Solingen Wald};
\node[anchor=north west] (title) at ($(org.south west)+(0cm,-0.5cm)$) {\large{Titel der Maßnahme:}}; \node[anchor=north west] (title) at ($(org.south west)+(0cm,-0.5cm)$) {\large{Titel der Maßnahme:}};
\draw (title.south east -| options-2-2.south west) -- (title.south east -| options-2-4.south east) node[formfill] {<<<$data->eventName>>>}; \draw (title.south east -| options-2-2.south west) -- (title.south east -| options-2-4.south east) node[formfill] {<<<$eventName>>>};
\node[anchor=north west] (datefrom) at ($(title.south west)+(0cm,-0.5cm)$) {\large{Datum vom:}}; \node[anchor=north west] (datefrom) at ($(title.south west)+(0cm,-0.5cm)$) {\large{Datum vom:}};
\draw (datefrom.south east -| options-2-2.south west) -- ($(datefrom.south east -| options-2-2.south east) - (1,0cm)$) node[formfill] {<<<$data->niceEventFrom()>>>}; \draw (datefrom.south east -| options-2-2.south west) -- ($(datefrom.south east -| options-2-2.south east) - (1,0cm)$) node[formfill] {<<<$niceEventFrom()>>>};
\node[anchor=south west] (dateuntil) at (options-2-3.south west |- datefrom.south west) {\large{bis:}}; \node[anchor=south west] (dateuntil) at (options-2-3.south west |- datefrom.south west) {\large{bis:}};
\draw[label={east:aaa}] (dateuntil.south east) -- (datefrom.south east -| options-2-3.south east) node[formfill] {<<<$data->niceEventTo()>>>}; \draw[label={east:aaa}] (dateuntil.south east) -- (datefrom.south east -| options-2-3.south east) node[formfill] {<<<$niceEventUntil()>>>};
\path[fill=yellow] (datefrom.south -| OL) ++(0,-1.0) rectangle ($(datefrom.south -| OR) + (0,-1.5)$); \path[fill=yellow] (datefrom.south -| OL) ++(0,-1.0) rectangle ($(datefrom.south -| OR) + (0,-1.5)$);

View File

@ -25,7 +25,7 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::get('/initialize', InitializeFormAction::class)->name('initialize.form'); Route::get('/initialize', InitializeFormAction::class)->name('initialize.form');
Route::post('/initialize', InitializeAction::class)->name('initialize.store'); Route::post('/initialize', InitializeAction::class)->name('initialize.store');
Route::resource('member', MemberController::class); Route::resource('member', MemberController::class);
Route::resource('member.payment', PaymentController::class); Route::apiResource('member.payment', PaymentController::class);
Route::resource('allpayment', AllpaymentController::class); Route::resource('allpayment', AllpaymentController::class);
Route::resource('subscription', SubscriptionController::class); Route::resource('subscription', SubscriptionController::class);
Route::post('/member/{member}/confirm', MemberConfirmController::class); Route::post('/member/{member}/confirm', MemberConfirmController::class);

View File

@ -2,7 +2,7 @@
namespace Tests\Feature\Bill; namespace Tests\Feature\Bill;
use App\Bill\BillSettings; use App\Letter\LetterSettings;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase; use Tests\TestCase;
@ -13,7 +13,7 @@ class SettingTest extends TestCase
public function testSettingIndex(): void public function testSettingIndex(): void
{ {
$this->withoutExceptionHandling()->login()->loginNami(); $this->withoutExceptionHandling()->login()->loginNami();
BillSettings::fake([ LetterSettings::fake([
'from_long' => 'DPSG Stamm Muster', 'from_long' => 'DPSG Stamm Muster',
'from' => 'Stamm Muster', 'from' => 'Stamm Muster',
'mobile' => '+49 176 55555', 'mobile' => '+49 176 55555',
@ -74,7 +74,7 @@ class SettingTest extends TestCase
]); ]);
$response->assertRedirect('/setting/bill'); $response->assertRedirect('/setting/bill');
$settings = app(BillSettings::class); $settings = app(LetterSettings::class);
$this->assertEquals('DPSG Stamm Muster', $settings->from_long); $this->assertEquals('DPSG Stamm Muster', $settings->from_long);
} }
} }

View File

@ -0,0 +1,44 @@
<?php
namespace Tests\Feature;
use App\Country;
use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use Zoomyboy\Tex\Tex;
class ContributionTest extends TestCase
{
use DatabaseTransactions;
/**
* @testWith ["App\\Contribution\\SolingenDocument", ["Super tolles Lager", "Max Muster", "Jane Muster", "15.06.1991"]]
* ["App\\Contribution\\DvDocument", ["Muster, Max", "Muster, Jane", "15.06.1991", "42777 SG"]]
*
* @param array<int, string> $bodyChecks
*/
public function testItCompilesContributionDocuments(string $type, array $bodyChecks): void
{
$this->withoutExceptionHandling();
Tex::spy();
$this->login()->loginNami();
$country = Country::factory()->create();
$member1 = Member::factory()->defaults()->create(['firstname' => 'Max', 'lastname' => 'Muster']);
$member2 = Member::factory()->defaults()->create(['firstname' => 'Jane', 'lastname' => 'Muster']);
$response = $this->call('GET', '/contribution/generate', [
'country' => $country->id,
'dateFrom' => '1991-06-15',
'dateUntil' => '1991-06-16',
'eventName' => 'Super tolles Lager',
'members' => [$member1->id, $member2->id],
'type' => $type,
'zipLocation' => '42777 SG',
]);
$response->assertSessionDoesntHaveErrors();
$response->assertOk();
Tex::assertCompiled($type, fn ($document) => $document->hasAllContent($bodyChecks));
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Tests\Feature\Letter;
use App\Letter\Actions\LetterSendAction;
use App\Letter\BillDocument;
use App\Member\Member;
use App\Payment\Payment;
use App\Payment\PaymentMail;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Storage;
use Mail;
use Tests\TestCase;
use Zoomyboy\Tex\Tex;
class LetterSendActionTest extends TestCase
{
use DatabaseTransactions;
public Member $member;
public function setUp(): void
{
parent::setUp();
Storage::fake('local');
$this->withoutExceptionHandling();
$this->login()->loginNami();
$this->member = Member::factory()
->defaults()
->has(Payment::factory()->notPaid()->nr('1997')->subscription('tollerbeitrag', 5400))
->emailBillKind()
->create(['firstname' => 'Lah', 'lastname' => 'Mom', 'email' => 'peter@example.com']);
}
public function testItCanCreatePdfPayments(): void
{
Mail::fake();
LetterSendAction::run();
Mail::assertSent(PaymentMail::class, fn ($mail) => Storage::path('rechnung-fur-mom.pdf') === $mail->filename);
}
public function testItCanCompileAttachment(): void
{
Mail::fake();
Tex::spy();
LetterSendAction::run();
Tex::assertCompiled(BillDocument::class, fn ($document) => 'Mom' === $document->pages->first()->familyName
&& $document->pages->first()->getPositions() === ['Beitrag 1997 für Lah Mom (tollerbeitrag)' => '54.00']
);
}
}

View File

@ -3,10 +3,10 @@
namespace Tests\Feature\Member; namespace Tests\Feature\Member;
use App\Activity; use App\Activity;
use App\Bill\BillKind;
use App\Country; use App\Country;
use App\Fee; use App\Fee;
use App\Gender; use App\Gender;
use App\Letter\BillKind;
use App\Member\CreateJob; use App\Member\CreateJob;
use App\Member\Member; use App\Member\Member;
use App\Nationality; use App\Nationality;

View File

@ -5,12 +5,11 @@ namespace Tests\Feature\Pdf;
use App\Country; use App\Country;
use App\Fee; use App\Fee;
use App\Group; use App\Group;
use App\Letter\BillDocument;
use App\Letter\DocumentFactory;
use App\Member\Member; use App\Member\Member;
use App\Nationality; use App\Nationality;
use App\Payment\Subscription; use App\Payment\Subscription;
use App\Pdf\BillType;
use App\Pdf\PdfGenerator;
use App\Pdf\PdfRepositoryFactory;
use Carbon\Carbon; use Carbon\Carbon;
use Database\Factories\Member\MemberFactory; use Database\Factories\Member\MemberFactory;
use Database\Factories\Payment\PaymentFactory; use Database\Factories\Payment\PaymentFactory;
@ -38,18 +37,18 @@ class GenerateTest extends TestCase
'no_pdf_when_no_bill' => [ 'no_pdf_when_no_bill' => [
'members' => [ 'members' => [
[ [
'factory' => fn (MemberFactory $member): MemberFactory => $member, 'factory' => fn (MemberFactory $member) => $member,
'payments' => [], 'payments' => [],
], ],
], ],
'urlCallable' => fn (Collection $members): int => $members->first()->id, 'urlCallable' => fn (Collection $members): int => $members->first()->id,
'type' => BillType::class, 'type' => BillDocument::class,
'filename' => null, 'filename' => null,
], ],
'bill_for_single_member_when_no_bill_received_yet' => [ 'bill_for_single_member_when_no_bill_received_yet' => [
'members' => [ 'members' => [
[ [
'factory' => fn (MemberFactory $member): MemberFactory => $member 'factory' => fn (MemberFactory $member) => $member
->state([ ->state([
'firstname' => '::firstname::', 'firstname' => '::firstname::',
'lastname' => '::lastname::', 'lastname' => '::lastname::',
@ -58,7 +57,7 @@ class GenerateTest extends TestCase
'location' => '::location::', 'location' => '::location::',
]), ]),
'payments' => [ 'payments' => [
fn (PaymentFactory $payment): PaymentFactory => $payment fn (PaymentFactory $payment) => $payment
->notPaid() ->notPaid()
->nr('1995') ->nr('1995')
->subscription('::subName::', 1500), ->subscription('::subName::', 1500),
@ -66,7 +65,7 @@ class GenerateTest extends TestCase
], ],
], ],
'urlCallable' => fn (Collection $members): int => $members->first()->id, 'urlCallable' => fn (Collection $members): int => $members->first()->id,
'type' => BillType::class, 'type' => BillDocument::class,
'filename' => 'rechnung-fur-lastname.pdf', 'filename' => 'rechnung-fur-lastname.pdf',
'output' => [ 'output' => [
'Rechnung', 'Rechnung',
@ -79,19 +78,19 @@ class GenerateTest extends TestCase
'bill_has_deadline' => [ 'bill_has_deadline' => [
'members' => [ 'members' => [
[ [
'factory' => fn (MemberFactory $member): MemberFactory => $member 'factory' => fn (MemberFactory $member) => $member
->state([ ->state([
'firstname' => '::firstname::', 'firstname' => '::firstname::',
'lastname' => '::lastname::', 'lastname' => '::lastname::',
]), ]),
'payments' => [ 'payments' => [
fn (PaymentFactory $payment): PaymentFactory => $payment fn (PaymentFactory $payment) => $payment
->nr('A')->notPaid()->subscription('::subName::', 1500), ->nr('A')->notPaid()->subscription('::subName::', 1500),
], ],
], ],
], ],
'urlCallable' => fn (Collection $members): int => $members->first()->id, 'urlCallable' => fn (Collection $members): int => $members->first()->id,
'type' => BillType::class, 'type' => BillDocument::class,
'filename' => 'rechnung-fur-lastname.pdf', 'filename' => 'rechnung-fur-lastname.pdf',
'output' => [ 'output' => [
'29.04.2021', '29.04.2021',
@ -100,7 +99,7 @@ class GenerateTest extends TestCase
'families' => [ 'families' => [
'members' => [ 'members' => [
[ [
'factory' => fn (MemberFactory $member): MemberFactory => $member 'factory' => fn (MemberFactory $member) => $member
->state([ ->state([
'firstname' => '::firstname1::', 'firstname' => '::firstname1::',
'lastname' => '::lastname::', 'lastname' => '::lastname::',
@ -109,12 +108,12 @@ class GenerateTest extends TestCase
'location' => '::location::', 'location' => '::location::',
]), ]),
'payments' => [ 'payments' => [
fn (PaymentFactory $payment): PaymentFactory => $payment fn (PaymentFactory $payment) => $payment
->nr('::nr::')->notPaid()->subscription('::subName::', 1500), ->nr('::nr::')->notPaid()->subscription('::subName::', 1500),
], ],
], ],
[ [
'factory' => fn (MemberFactory $member): MemberFactory => $member 'factory' => fn (MemberFactory $member) => $member
->state([ ->state([
'firstname' => '::firstname2::', 'firstname' => '::firstname2::',
'lastname' => '::lastname::', 'lastname' => '::lastname::',
@ -123,13 +122,13 @@ class GenerateTest extends TestCase
'location' => '::location::', 'location' => '::location::',
]), ]),
'payments' => [ 'payments' => [
fn (PaymentFactory $payment): PaymentFactory => $payment fn (PaymentFactory $payment) => $payment
->nr('::nr2::')->notPaid()->subscription('::subName2::', 1600), ->nr('::nr2::')->notPaid()->subscription('::subName2::', 1600),
], ],
], ],
], ],
'urlCallable' => fn (Collection $members): int => $members->first()->id, 'urlCallable' => fn (Collection $members): int => $members->first()->id,
'type' => BillType::class, 'type' => BillDocument::class,
'filename' => 'rechnung-fur-lastname.pdf', 'filename' => 'rechnung-fur-lastname.pdf',
'output' => [ 'output' => [
'::nr::', '::nr::',
@ -155,7 +154,7 @@ class GenerateTest extends TestCase
$urlId = call_user_func($urlCallable, $members); $urlId = call_user_func($urlCallable, $members);
$member = Member::find($urlId); $member = Member::find($urlId);
$repo = app(PdfRepositoryFactory::class)->fromSingleRequest($type, $member); $repo = app(DocumentFactory::class)->fromSingleRequest($type, $member);
if (null === $filename) { if (null === $filename) {
$this->assertNull($repo); $this->assertNull($repo);
@ -163,7 +162,7 @@ class GenerateTest extends TestCase
return; return;
} }
$content = app(PdfGenerator::class)->setRepository($repo)->compileView(); $content = $repo->renderBody();
foreach ($output as $out) { foreach ($output as $out) {
$this->assertStringContainsString($out, $content); $this->assertStringContainsString($out, $content);

View File

@ -0,0 +1,68 @@
<?php
namespace Tests\Feature\Sendpayment;
use App\Letter\BillDocument;
use App\Member\Member;
use App\Payment\Payment;
use App\Payment\Status;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use Zoomyboy\Tex\Tex;
class SendpaymentTest extends TestCase
{
use DatabaseTransactions;
public function testItCanViewSendpaymentPage(): void
{
$this->withoutExceptionHandling();
$this->login()->loginNami();
$response = $this->get(route('sendpayment.create'));
$response->assertOk();
$this->assertInertiaHas('Rechnungen versenden', $response, 'types.0.link.label');
$href = $this->inertia($response, 'types.0.link.href');
$this->assertStringContainsString('BillDocument', $href);
}
public function testItCanCreatePdfPayments(): void
{
Tex::spy();
$this->withoutExceptionHandling();
$this->login()->loginNami();
$member = Member::factory()
->defaults()
->has(Payment::factory()->notPaid()->nr('1997')->subscription('tollerbeitrag', 5400))
->has(Payment::factory()->paid()->nr('1998')->subscription('bezahltdesc', 5800))
->postBillKind()
->create();
$response = $this->call('GET', route('sendpayment.pdf'), ['type' => 'App\\Letter\\BillDocument']);
$response->assertOk();
$this->assertEquals(Status::firstWhere('name', 'Rechnung gestellt')->id, $member->payments->firstWhere('nr', '1997')->status_id);
$this->assertEquals(Status::firstWhere('name', 'Rechnung beglichen')->id, $member->payments->firstWhere('nr', '1998')->status_id);
Tex::assertCompiled(BillDocument::class, fn ($document) => $document->hasAllContent(['1997', 'tollerbeitrag', '54.00'])
&& $document->missesAllContent(['1998', 'bezahltdesc', '58.00'])
);
}
public function testItDoesntCreatePdfsWhenUserHasEmail(): void
{
Tex::spy();
$this->withoutExceptionHandling();
$this->login()->loginNami();
$member = Member::factory()
->defaults()
->has(Payment::factory()->notPaid()->nr('1997')->subscription('tollerbeitrag', 5400))
->emailBillKind()
->create();
$response = $this->call('GET', route('sendpayment.pdf'), ['type' => 'App\\Letter\\BillDocument']);
$response->assertStatus(204);
Tex::assertNotCompiled(BillDocument::class);
}
}