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

View File

@ -6,10 +6,11 @@ use App\Country;
use App\Http\Controllers\Controller;
use App\Member\Member;
use App\Member\MemberResource;
use App\Pdf\PdfGenerator;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use Zoomyboy\Tex\BaseCompiler;
use Zoomyboy\Tex\Tex;
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\Member\Member;
use App\Pdf\EnvType;
use App\Pdf\PdfRepository;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
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 string $dateFrom,
public string $dateUntil,
public string $zipLocation,
public ?Country $country,
public array $members,
public Collection $members,
public ?string $filename = '',
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
{
return new self(
@ -31,13 +38,13 @@ class DvData extends Data implements PdfRepository
dateUntil: $request->dateUntil,
zipLocation: $request->zipLocation,
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
@ -69,31 +76,19 @@ class DvData extends Data implements PdfRepository
return (string) $member->getAge();
}
public function countryName(): 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
public function basename(): string
{
return 'zuschuesse-dv';
}
public function getView(): string
public function view(): string
{
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
@ -103,8 +98,8 @@ class DvData extends Data implements PdfRepository
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;
use App\Member\Member;
use App\Pdf\EnvType;
use App\Pdf\PdfRepository;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
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(
public string $eventName,
final private function __construct(
public string $dateFrom,
public string $dateUntil,
public string $zipLocation,
/** @var array<int, int> */
public array $members,
public ?string $filename = '',
public string $eventName,
public string $type = 'F',
) {
}
public static function fromRequest(Request $request): self
public static function fromRequest(Request $request): static
{
return new self(
eventName: $request->eventName,
return new static(
dateFrom: $request->dateFrom,
dateUntil: $request->dateUntil,
zipLocation: $request->zipLocation,
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
{
$output = '';
@ -48,45 +73,18 @@ class SolingenData extends Data implements PdfRepository
return $firstRow."\n".$secondRow;
}
public function members(): Collection
{
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
public function basename(): string
{
return 'zuschuesse-solingen-'.Str::slug($this->eventName);
}
public function getView(): string
public function view(): string
{
return 'tex.zuschuss-stadt';
}
public function getTemplate(): ?string
public function getEngine(): Engine
{
return null;
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getScript(): EnvType
{
return EnvType::PDFLATEX;
return Engine::PDFLATEX;
}
}

View File

@ -6,9 +6,6 @@ use App\Activity;
use App\Course\Models\Course;
use App\Member\Member;
use App\Member\MemberResource;
use App\Payment\ActionFactory;
use App\Payment\Payment;
use App\Payment\PaymentResource;
use App\Payment\Status;
use App\Payment\Subscription;
use App\Region;
@ -43,34 +40,4 @@ class MemberView
})->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
namespace App\Bill;
namespace App\Letter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
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
namespace App\Bill;
namespace App\Letter;
use App\Setting\LocalSettings;
class BillSettings extends LocalSettings
class LetterSettings extends LocalSettings
{
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
namespace App\Pdf;
namespace App\Letter;
use App\Member\Member;
use App\Payment\Payment;
use Illuminate\Database\Eloquent\Relations\HasMany;
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
{
return 'Erinnerung erstellen';
@ -37,21 +30,11 @@ class RememberType extends Repository implements LetterRepository
return $this->filename;
}
public function getScript(): EnvType
{
return EnvType::XELATEX;
}
public function getView(): string
public function view(): string
{
return 'tex.remember';
}
public function getTemplate(): ?string
{
return 'default';
}
public function getPositions(Collection $page): array
{
$memberIds = $page->pluck('id')->toArray();
@ -65,11 +48,6 @@ class RememberType extends Repository implements LetterRepository
})->toArray();
}
public function getFamilyName(Collection $page): string
{
return $page->first()->lastname;
}
public function getAddress(Collection $page): string
{
return $page->first()->address;
@ -92,10 +70,10 @@ class RememberType extends Repository implements LetterRepository
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';
}
@ -122,4 +100,14 @@ class RememberType extends Repository implements LetterRepository
{
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
namespace App\Bill;
namespace App\Letter;
use Inertia\Inertia;
use Inertia\Response;
@ -13,7 +13,7 @@ class SettingIndexAction
/**
* @return array<string, string>
*/
public function handle(BillSettings $settings): array
public function handle(LetterSettings $settings): array
{
return [
'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('title', 'Rechnungs-Einstellungen');

View File

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

View File

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

View File

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

View File

@ -2,31 +2,19 @@
namespace App\Payment;
use App\Member\Member;
use App\Pdf\PdfRepository;
use App\Pdf\PdfRepositoryFactory;
use App\Letter\DocumentFactory;
use App\Letter\Letter;
use Illuminate\Support\Collection;
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
{
return app(PdfRepositoryFactory::class)->getTypes()->map(function (PdfRepository $repo) {
return app(DocumentFactory::class)->getTypes()->map(function (Letter $repo) {
return [
'link' => [
'href' => route('sendpayment.pdf', ['type' => get_class($repo)]),
'label' => $repo->allLabel(),
'label' => $repo->sendAllLabel(),
],
'text' => $repo->getDescription(),
];

View File

@ -3,25 +3,12 @@
namespace App\Payment;
use App\Http\Controllers\Controller;
use App\Http\Views\MemberView;
use App\Member\Member;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Response;
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
{
$member->createPayment($request->validate([
@ -33,17 +20,6 @@ class PaymentController extends Controller
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
{
$payment->update($request->validate([

View File

@ -2,7 +2,7 @@
namespace App\Payment;
use App\Pdf\LetterRepository;
use App\Letter\Letter;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
@ -13,18 +13,20 @@ class PaymentMail extends Mailable
use Queueable;
use SerializesModels;
public LetterRepository $repo;
public Letter $letter;
public string $filename;
public string $salutation;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(LetterRepository $repo, string $filename)
public function __construct(Letter $letter, string $filename)
{
$this->letter = $letter;
$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()
{
$template = Str::snake(class_basename($this->repo));
$template = Str::snake(class_basename($this->letter));
return $this->markdown('mail.payment.'.$template)
->attach($this->filename)
->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;
use App\Http\Controllers\Controller;
use App\Pdf\PdfGenerator;
use App\Pdf\PdfRepositoryFactory;
use App\Letter\DocumentFactory;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;
use Zoomyboy\Tex\Tex;
class SendpaymentController extends Controller
{
@ -28,13 +28,15 @@ class SendpaymentController extends Controller
*/
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();
app(PdfRepositoryFactory::class)->afterAll($request->type, 'Post');
if (is_null($repo)) {
return response()->noContent();
}
return null === $repo
? response()->noContent()
: $pdfFile;
$pdfFile = Tex::compile($repo);
app(DocumentFactory::class)->afterAll($request->type, 'Post');
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;
use App\Http\Controllers\Controller;
use App\Letter\DocumentFactory;
use App\Member\Member;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Zoomyboy\Tex\Tex;
class MemberPdfController extends Controller
{
@ -15,10 +17,10 @@ class MemberPdfController extends Controller
*/
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()
: 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;
use App\Bill\BillSettings;
use App\Letter\LetterSettings;
use App\Mailman\MailmanSettings;
use Illuminate\Support\ServiceProvider;
@ -25,7 +25,7 @@ class SettingServiceProvider extends ServiceProvider
*/
public function boot()
{
app(SettingFactory::class)->register(BillSettings::class);
app(SettingFactory::class)->register(LetterSettings::class);
app(SettingFactory::class)->register(MailmanSettings::class);
}
}

View File

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

View File

@ -5,6 +5,7 @@ namespace Database\Factories\Member;
use App\Country;
use App\Fee;
use App\Group;
use App\Letter\BillKind;
use App\Member\Member;
use App\Nationality;
use App\Payment\Payment;
@ -60,6 +61,20 @@ class MemberFactory extends Factory
->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
{
return $this->state(['nami_id' => $namiId]);

View File

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

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

View File

@ -1,5 +1,5 @@
@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.

View File

@ -1,5 +1,5 @@
@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.

View File

@ -1,19 +1,19 @@
\documentclass[silvaletter,12pt]{scrlttr2}
\setkomavar{subject}{<<< $data->getSubject() >>>}
\setkomavar{subject}{<<< $subject >>>}
\begin{document}
@foreach($data->pages as $page)
\begin{letter}{Familie <<< $data->getFamilyName($page) >>>\\<<< $data->getAddress($page) >>>\\<<< $data->getZip($page) >>> <<< $data->getLocation($page) >>>}
@foreach($pages as $page)
\begin{letter}{Familie <<< $page->familyName >>>\\<<< $page->address >>>\\<<< $page->zip >>> <<< $page->location >>>}
\sffamily
\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:
\begin{center}
\begin{tabular}{@{}p{0.5\textwidth}|r}
@foreach($data->getPositions($page) as $desc => $price)
@foreach($page->positions as $desc => $price)
\product{<<< $desc >>>}{<<< $price >>>}
@endforeach
\hline
@ -21,13 +21,13 @@
\end{tabular}
\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}
Kontoinhaber: & DPSG Stamm Silva \\
IBAN: & DE40 3425 0000 0000 2145 51 \\
Bic: & SOLSDE33XXX \\
Verwendungszweck: & <<<$data->getUsage($page)>>>
Verwendungszweck: & <<<$page->usage>>>
\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.

View File

@ -1,19 +1,19 @@
\documentclass[silvaletter,12pt]{scrlttr2}
\setkomavar{subject}{<<< $data->getSubject() >>>}
\setkomavar{subject}{<<< $subject >>>}
\begin{document}
@foreach($data->pages as $page)
\begin{letter}{Familie <<< $data->getFamilyName($page) >>>\\<<< $data->getAddress($page) >>>\\<<< $data->getZip($page) >>> <<< $data->getLocation($page) >>>}
@foreach($pages as $page)
\begin{letter}{Familie <<< $page->familyName >>>\\<<< $page->address >>>\\<<< $page->zip >>> <<< $page->location >>>}
\sffamily
\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:
\begin{center}
\begin{tabular}{@{}p{0.5\textwidth}|r}
@foreach($data->getPositions($page) as $desc => $price)
@foreach($page->positions as $desc => $price)
\product{<<< $desc >>>}{<<< $price >>>}
@endforeach
\hline
@ -21,13 +21,13 @@
\end{tabular}
\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}
Kontoinhaber: & DPSG Stamm Silva \\
IBAN: & DE40 3425 0000 0000 2145 51 \\
Bic: & SOLSDE33XXX \\
Verwendungszweck: & <<<$data->getUsage($page)>>>
Verwendungszweck: & <<<$page->usage>>>
\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.

View File

@ -13,21 +13,21 @@
\begin{document}
\noindent \sffamily
@foreach($data->members()->chunk(17) as $chunk)
@foreach($members as $chunk)
\begin{tikzpicture}[remember picture,overlay,yscale=-1]
\node[anchor=base west] at (38mm,41.62mm) {\bfseries{\large{<<<!!$data->dateRange()!!>>>}}};
\node[anchor=base west] at (135.2mm,41.62mm) {\bfseries{\large{<<<!!$data->zipLocation!!>>>}}};
\node[anchor=base west] at (242.7mm,41.62mm) {\bfseries{\large{<<<!!$data->countryName()!!>>>}}};
\node[anchor=base west] at (38mm,41.62mm) {\bfseries{\large{<<<!!$dateRange!!>>>}}};
\node[anchor=base west] at (135.2mm,41.62mm) {\bfseries{\large{<<<!!$zipLocation!!>>>}}};
\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) {};
@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=18mm, align=center] at ($(32.55mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$data->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=118mm, align=center] at ($(178.25mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$data->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 ($(269.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$data->memberAge($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>>>)$) {<<<$memberName($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>>>)$) {<<<$memberGender($member)>>>};
\node[anchor=base, text width=16mm, align=center] at ($(269.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberAge($member)>>>};
@endforeach
\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) {};}}
\begin{document} \sffamily
@foreach($data->members()->chunk(14) as $chunk)
@foreach($memberModels as $chunk)
\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}};
\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);}};
@ -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};
\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:}};
\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:}};
\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)$);

View File

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

View File

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

View File

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