422 lines
18 KiB
PHP
422 lines
18 KiB
PHP
<?php
|
||
|
||
namespace App\Resources;
|
||
|
||
use App\Enumerations\ParticipationFeeType;
|
||
use App\Enumerations\ParticipationType;
|
||
use App\Enumerations\VatPricingMode;
|
||
use App\Models\Event;
|
||
use App\ValueObjects\Amount;
|
||
use DateTime;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Http\Resources\Json\JsonResource;
|
||
|
||
class EventResource extends JsonResource{
|
||
private Event $event;
|
||
|
||
public function __construct(Event $event) {
|
||
$this->event = $event;
|
||
}
|
||
|
||
public function toArray(Request $request) : array
|
||
{
|
||
$duration = $this->event->end_date->diff($this->event->start_date)->days + 1;
|
||
|
||
$returnArray = [
|
||
'id' => $this->event->id,
|
||
'name' => $this->event->name,
|
||
'identifier' => $this->event->identifier,
|
||
'url' => 'https://' . app('tenant')->url . '/event/' . $this->event->identifier . '/signup',
|
||
'urlShort' => 'https://' . app('tenant')->url . '/event/' . $this->event->identifier,
|
||
'location' => $this->event->location,
|
||
'postalCode' => $this->event->postal_code,
|
||
'email' => $this->event->email,
|
||
'accountOwner' => $this->event->account_owner,
|
||
'accountIban' => $this->event->account_iban,
|
||
'alcoholicsAge' => $this->event->alcoholics_age,
|
||
'sendWeeklyReports' => $this->event->send_weekly_report,
|
||
'registrationAllowed' => $this->event->registration_allowed && new \DateTime() <= $this->event->start_date,
|
||
'archived' => $this->event->archived,
|
||
'earlyBirdEnd' => ['internal' => $this->event->early_bird_end->format('Y-m-d'), 'formatted' => $this->event->early_bird_end->format('d.m.Y')],
|
||
'registrationFinalEnd' => ['internal' => $this->event->registration_final_end->format('Y-m-d'), 'formatted' => $this->event->registration_final_end->format('d.m.Y')],
|
||
'refundAfterEarlyBirdEnd' => 100 - $this->event->early_bird_end_amount_increase,
|
||
];
|
||
|
||
$returnArray['swimmingPermissions'] = \App\Enumerations\SwimmingPermission::query()
|
||
->get()
|
||
->map(fn ($permission) => [
|
||
'slug' => $permission->slug,
|
||
'name' => $permission->name,
|
||
])
|
||
->toArray();
|
||
|
||
$returnArray['firstAidPermissions'] = \App\Enumerations\FirstAidPermission::query()
|
||
->get()
|
||
->map(fn ($permission) => [
|
||
'slug' => $permission->slug,
|
||
'name' => $permission->name,
|
||
])
|
||
->toArray();
|
||
|
||
|
||
foreach ([
|
||
ParticipationType::PARTICIPATION_TYPE_PARTICIPANT,
|
||
ParticipationType::PARTICIPATION_TYPE_TEAM,
|
||
ParticipationType::PARTICIPATION_TYPE_VOLUNTEER,
|
||
ParticipationType::PARTICIPATION_TYPE_OTHER,
|
||
] as $participationType) {
|
||
$_t =$this->getParticipants($participationType);
|
||
$returnArray['participants'][$participationType] = $_t;
|
||
$returnArray['count' . ucfirst($participationType)] = $_t['count'];
|
||
}
|
||
|
||
$returnArray['nameShort'] = $returnArray['name'];
|
||
if (strlen($returnArray['nameShort']) > 15) {
|
||
$returnArray['nameShort'] = substr($returnArray['nameShort'], 8, 13) . '...';
|
||
}
|
||
|
||
$returnArray['siblingReduction'] = $this->event->sibling_reduction ?? true;
|
||
$returnArray['costUnit'] = new CostUnitResource($this->event->costUnit()->first())->toArray(true);
|
||
$returnArray['solidarityPayment'] = $this->event->participation_fee_type === ParticipationFeeType::PARTICIPATION_FEE_TYPE_SOLIDARITY;
|
||
$returnArray['payPerDay'] = $this->event->pay_per_day;
|
||
$returnArray['maxAmount'] = $this->event->total_max_amount->getFormattedAmount();
|
||
$returnArray['arrivalDefault'] = $this->event->start_date->format('Y-m-d');
|
||
$returnArray['departureDefault'] = $this->event->end_date->format('Y-m-d');
|
||
$returnArray['eventBegin'] = $this->event->start_date->format('d.m.Y');
|
||
$returnArray['eventBeginInternal'] = $this->event->start_date;
|
||
$returnArray['eventEnd'] = $this->event->end_date->format('d.m.Y');
|
||
$returnArray['eventEndInternal'] = $this->event->end_date;
|
||
$returnArray['duration'] = $duration;
|
||
$returnArray['totalParticipantCount'] = $this->event->participants()->count();
|
||
|
||
$returnArray['supportPersonIndex'] = $this->event->support_per_person->toString();
|
||
$returnArray['supportPersonValue'] = $this->event->support_per_person->getAmount();
|
||
$returnArray['supportPerson'] = $this->calculateSupportPerPerson($returnArray['participants']);
|
||
|
||
$returnArray['income'] = $this->calculateIncomes($returnArray['participants'], $returnArray['supportPerson']['amount']);
|
||
|
||
|
||
$totalBalanceReal = new Amount(0, 'Euro');
|
||
$totalBalanceExpected = new Amount(0, 'Euro');
|
||
$totalBalanceEstimated = new Amount(0, 'Euro');
|
||
|
||
$totalBalanceReal->addAmount($returnArray['income']['real']['amount']);
|
||
$totalBalanceExpected->addAmount($returnArray['income']['expected']['amount']);
|
||
$totalBalanceEstimated->addAmount($returnArray['income']['expected']['amount']);
|
||
|
||
$totalBalanceReal->subtractAmount($returnArray['costUnit']['overAllAmount']['value']);
|
||
$totalBalanceExpected->subtractAmount($returnArray['costUnit']['overAllAmount']['value']);
|
||
$totalBalanceEstimated->subtractAmount($returnArray['costUnit']['overAllEstimatedAmount']['value']);
|
||
$returnArray['totalBalance'] = [
|
||
'real' => [
|
||
'value' => $totalBalanceReal->getAmount(),
|
||
'readable' => $totalBalanceReal->toString(),
|
||
], 'expected' => [
|
||
'value' => $totalBalanceExpected->getAmount(),
|
||
'readable' => $totalBalanceExpected->toString(),
|
||
],
|
||
'estimated' => [
|
||
'value' => $totalBalanceEstimated->getAmount(),
|
||
'readable' => $totalBalanceEstimated->toString(),
|
||
]
|
||
];
|
||
|
||
$returnArray['flatSupport'] = $this->event->support_flat->toString();
|
||
|
||
$returnArray['flatSupportEdit'] = $this->event->support_flat->getFormattedAmount();
|
||
$returnArray['supportPersonEdit'] = $this->event->support_per_person->getFormattedAmount();
|
||
$returnArray['managers'] = $this->event->eventManagers()->get()->map(fn($user) => new UserResource($user))->toArray();
|
||
|
||
$returnArray['supportPersonCalced'] = $this->event->support_per_person->toString();
|
||
$returnArray['contributingLocalGroups'] = $this->event->localGroups
|
||
->map(fn ($localGroup) => new LocalGroupResource($localGroup)->toArray($request))
|
||
->toArray();
|
||
|
||
$returnArray['eatingHabits'] = $this->event->eatingHabits()->get()->map(
|
||
fn($eatingHabit) => new EatingHabitResource($eatingHabit))->toArray();
|
||
|
||
$returnArray['paymentMethods'] = $this->event->paymentMethods()->get()->map(
|
||
fn($paymentMethod) => new AvailablePaymentMethodResource($paymentMethod)->toArray($request))->toArray();
|
||
|
||
// Veranstaltungsspezifische Pflicht-Auswahlfragen (Teilnahmeoptionen); leeres Array => Schritt entfällt.
|
||
$returnArray['participationOptions'] = $this->event->participation_options ?? [];
|
||
|
||
// Buchbare Zusätze (Event-Addons); leeres Array => Schritt entfällt. Preis 0 => "Kostenfrei".
|
||
$returnArray['addons'] = collect($this->event->addons ?? [])->map(function ($addon) {
|
||
$price = (float)($addon['price'] ?? 0);
|
||
return [
|
||
'key' => $addon['key'] ?? '',
|
||
'title' => $addon['title'] ?? '',
|
||
'description' => $addon['description'] ?? '',
|
||
'price' => $price,
|
||
'priceReadable' => new Amount($price, 'Euro')->toString(),
|
||
'free' => $price <= 0,
|
||
'flat' => (bool)($addon['flat'] ?? false),
|
||
];
|
||
})->toArray();
|
||
|
||
|
||
$returnArray['participationTypes'] = [];
|
||
|
||
$multiplier = $this->getMultiplier();
|
||
|
||
for ($i = 1; $i <= 4; $i++) {
|
||
$returnArray['participationFee_' . $i] = [
|
||
'active' => false,
|
||
'name' => '',
|
||
'description' => '',
|
||
'amount_standard' => null,
|
||
'amount_reduced' => null,
|
||
'amount_solidarity' => null,
|
||
'type' => null,
|
||
];
|
||
|
||
if ($this->event->{'participation_fee_' . $i} === null) {
|
||
continue;
|
||
}
|
||
|
||
$participationFee = $this->event->{'participationFee' . $i}()->first();
|
||
|
||
$feeType = [
|
||
'active' => true,
|
||
'amount_standard_edit' => $participationFee->amount_standard->getFormattedAmount(),
|
||
'amount_standard' => [
|
||
'internal' => [
|
||
'amount' => $participationFee->amount_standard->getAmount(),
|
||
'currency' => $participationFee->amount_standard->getCurrency(),
|
||
],
|
||
'readable' => $participationFee->amount_standard->multiply($multiplier)->toString(),
|
||
],
|
||
'amount_reduced_edit' => $participationFee->amount_reduced === null ? null : $participationFee->amount_reduced->getFormattedAmount(),
|
||
'amount_reduced' => $participationFee->amount_reduced === null
|
||
? null
|
||
: [
|
||
'internal' => [
|
||
'amount' => $participationFee->amount_reduced->getAmount(),
|
||
'currency' => $participationFee->amount_reduced->getCurrency(),
|
||
],
|
||
'readable' => $participationFee->amount_reduced->multiply($multiplier)->toString(),
|
||
],
|
||
'amount_solidarity_edit' => $participationFee->amount_solidarity === null ? null : $participationFee->amount_solidarity->getFormattedAmount(),
|
||
'amount_solidarity' => $participationFee->amount_solidarity === null
|
||
? null
|
||
: [
|
||
'internal' => [
|
||
'amount' => $participationFee->amount_solidarity->getAmount(),
|
||
'currency' => $participationFee->amount_solidarity->getCurrency(),
|
||
],
|
||
'readable' => $participationFee->amount_solidarity->multiply($multiplier)->toString(),
|
||
],
|
||
'name' => $participationFee->name,
|
||
'description' => $participationFee->description,
|
||
'type' => (new ParticipationTypeResource($participationFee->type()->first()))->toArray($request),
|
||
];
|
||
|
||
$returnArray['participationFee_' . $i] = $feeType;
|
||
$returnArray['participationTypes'][] = $feeType;
|
||
|
||
}
|
||
|
||
return $returnArray;
|
||
}
|
||
|
||
public function getMultiplier() : float {
|
||
$earlyBirdEnd = $this->event->early_bird_end;
|
||
if ($earlyBirdEnd > now()) {
|
||
return 1;
|
||
}
|
||
|
||
return 1 + $this->event->early_bird_end_amount_increase / 100;
|
||
}
|
||
|
||
public function calculateSupportPerPerson(array $participants) : array {
|
||
if ($this->event->support_per_person === null) {
|
||
return [
|
||
'amount' => new Amount(0, 'Euro'),
|
||
'readable' => '0,00'
|
||
];
|
||
}
|
||
|
||
$amount = $this->event->support_per_person;
|
||
$multiplier = 0;
|
||
|
||
|
||
|
||
foreach ($participants as $participationType => $participantData) {
|
||
if (isset($participantData['participants']) && count($participantData['participants']) > 0) {
|
||
foreach ($participantData['participants'] as $participant) {
|
||
$multiplier += $participant->toResource()->toArray(new Request())['presenceDays']['support'];
|
||
}
|
||
}
|
||
}
|
||
|
||
$amount->multiply($multiplier);
|
||
|
||
return [
|
||
'amount' => $amount,
|
||
'readable' => $amount->toString()
|
||
];
|
||
|
||
|
||
}
|
||
|
||
public function calculateIncomes(array $participants, Amount $supportPerPerson) : array {
|
||
$expectedAmount = new Amount($supportPerPerson->getAmount(), 'Euro');
|
||
$expectedAmount->addAmount($this->event->support_flat);
|
||
|
||
$realAmount = new Amount($supportPerPerson->getAmount(), 'Euro');
|
||
$realAmount->addAmount($this->event->support_flat);
|
||
foreach ($participants as $participationType => $participantData) {
|
||
$expectedAmount->addAmount(new Amount($participantData['amount']['expected']['value'], 'Euro'));
|
||
$realAmount->addAmount(new Amount($participantData['amount']['paid']['value'], 'Euro'));
|
||
}
|
||
|
||
return ['real' => [
|
||
'amount' => $realAmount,
|
||
'readable' => $realAmount->toString()
|
||
],
|
||
'expected' => [
|
||
'amount' => $expectedAmount,
|
||
'readable' => $expectedAmount->toString(),
|
||
]
|
||
];
|
||
}
|
||
|
||
public function getParticipants(string $participationType) : array {
|
||
$returnData = [];
|
||
$returnData['amount'] = [
|
||
'expected' => new Amount(0, 'Euro'),
|
||
'paid' => new Amount(0, 'Euro')
|
||
];
|
||
|
||
$returnData['count'] = 0;
|
||
|
||
foreach ($this->event->participants()->where(
|
||
[
|
||
'participation_type' => $participationType,
|
||
'unregistered_at' => null
|
||
]
|
||
|
||
)->get() as $participant)
|
||
{
|
||
$returnData['count']++;
|
||
$returnData['participants'][] = $participant;
|
||
|
||
if ($participant->amount !== null) {
|
||
$returnData['amount']['expected'] = $returnData['amount']['expected']->addAmount($participant->amount);
|
||
}
|
||
|
||
if ($participant->amount_paid !== null) {
|
||
$returnData['amount']['paid'] = $returnData['amount']['paid']->addAmount($participant->amount_paid);
|
||
}
|
||
|
||
};
|
||
|
||
$returnData['amount']['expected'] =
|
||
[
|
||
'value' => $returnData['amount']['expected']->getAmount(),
|
||
'readable' => $returnData['amount']['expected']->toString()
|
||
];
|
||
|
||
$returnData['amount']['paid'] =
|
||
[
|
||
'value' => $returnData['amount']['paid']->getAmount(),
|
||
'readable' => $returnData['amount']['paid']->toString()
|
||
];
|
||
|
||
return $returnData;
|
||
}
|
||
|
||
|
||
public function calculateAmount(
|
||
string $participationType,
|
||
string $feeType,
|
||
DateTime $arrival,
|
||
DateTime $departure,
|
||
bool $hasSibling
|
||
) : Amount {
|
||
$fee = collect([
|
||
$this->event->participationFee1,
|
||
$this->event->participationFee2,
|
||
$this->event->participationFee3,
|
||
$this->event->participationFee4,
|
||
])->filter(fn ($participationFee) => $participationFee !== null)
|
||
->first(fn ($participationFee) => $participationFee->type === $participationType);
|
||
|
||
if ($fee === null) {
|
||
return new Amount(0, 'Euro');
|
||
}
|
||
|
||
/** @var Amount $basicFee */
|
||
$basicFee = $fee['amount_' . $feeType];
|
||
$basicFee = $basicFee->multiply($this->getMultiplier());
|
||
|
||
if ($this->event->pay_per_day) {
|
||
$days = $arrival->diff($departure)->days + 1;
|
||
$basicFee = $basicFee->multiply($days);
|
||
}
|
||
|
||
if ($hasSibling && $this->event->sibling_reduction) {
|
||
$basicFee = $basicFee->multiply(0.5);
|
||
}
|
||
|
||
// Bei bestehender USt-Pflicht und Netto-Preismodus die USt aufschlagen; gespeichert wird immer der
|
||
// finale Brutto-Betrag. Bei "inklusive" oder ohne Steuerpflicht bleibt der Beitrag unverändert.
|
||
if ($this->event->tax_liable && $this->event->vat_pricing_mode === VatPricingMode::AddOn->value) {
|
||
$basicFee = $basicFee->multiply(1 + $this->event->vat_rate / 100);
|
||
}
|
||
|
||
return new Amount(round($basicFee->getAmount(), 2), 'Euro');
|
||
}
|
||
|
||
/**
|
||
* Löst die gewählten Zusätze (Event-Addons) gegen die Event-Definition auf und berechnet je Position den
|
||
* Betrag: Pauschale => Preis, sonst Preis × Teilnahmetage. Bei USt-Pflicht + Netto-Preismodus (`add_on`)
|
||
* wird die USt aufgeschlagen (analog Beitrag); bei „inclusive"/keiner Pflicht bleibt der Preis unverändert.
|
||
* Kein Early-Bird-Aufschlag.
|
||
*
|
||
* @param array<int, string> $selectedKeys
|
||
* @return array{items: array<int, array{key: string, title: string, price: float, flat: bool, days: int, amount: float}>, total: Amount}
|
||
*/
|
||
public function resolveAddons(array $selectedKeys, DateTime $arrival, DateTime $departure): array
|
||
{
|
||
$days = $arrival->diff($departure)->days + 1;
|
||
$addsVat = $this->event->tax_liable && $this->event->vat_pricing_mode === VatPricingMode::AddOn->value;
|
||
|
||
$items = [];
|
||
$total = new Amount(0, 'Euro');
|
||
|
||
foreach ($this->event->addons ?? [] as $addon) {
|
||
$key = $addon['key'] ?? null;
|
||
if ($key === null || !in_array($key, $selectedKeys, true)) {
|
||
continue;
|
||
}
|
||
|
||
$price = (float)($addon['price'] ?? 0);
|
||
$flat = (bool)($addon['flat'] ?? false);
|
||
$usedDays = $flat ? 1 : $days;
|
||
|
||
$amount = $flat ? $price : $price * $days;
|
||
if ($addsVat) {
|
||
$amount *= 1 + $this->event->vat_rate / 100;
|
||
}
|
||
$amount = round($amount, 2);
|
||
|
||
$items[] = [
|
||
'key' => $key,
|
||
'title' => $addon['title'] ?? $key,
|
||
'price' => $price,
|
||
'flat' => $flat,
|
||
'days' => $usedDays,
|
||
'amount' => $amount,
|
||
];
|
||
|
||
$total->addAmount(new Amount($amount, 'Euro'));
|
||
}
|
||
|
||
return ['items' => $items, 'total' => $total];
|
||
}
|
||
}
|
||
|
||
|