Fixed tests
This commit is contained in:
parent
b717148058
commit
a856063380
app
Console
Contribution
Http/Views
Letter
Actions
BillDocument.phpBillKind.phpDocumentFactory.phpLetter.phpLetterSettings.phpMailRecipient.phpPage.phpRememberDocument.phpSettingIndexAction.phpSettingSaveAction.phpMember
Payment
ActionFactory.phpPaymentController.phpPaymentMail.phpPaymentSendCommand.phpSendpaymentController.php
Pdf
BillType.php
Data
EnvType.phpLetterRepository.phpMemberPdfController.phpPdfGenerator.phpPdfRepository.phpPdfRepositoryFactory.phpRepository.phpSetting
database
factories
migrations
packages
resources/views
mail/payment
tex
routes
tests/Feature
|
@ -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,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Bill;
|
||||
namespace App\Letter;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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, '.', '');
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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');
|
|
@ -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'] ?? '',
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Pdf;
|
||||
|
||||
enum EnvType: string
|
||||
{
|
||||
case XELATEX = 'XELATEX';
|
||||
case PDFLATEX = 'PDFLATEX';
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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}",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
|
@ -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]);
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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)$);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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']
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue