feat: consistent display of included services and prices
This commit is contained in:
@@ -76,10 +76,14 @@ class Pickup
|
|||||||
{
|
{
|
||||||
$label = $this->getLabel();
|
$label = $this->getLabel();
|
||||||
|
|
||||||
if (null === $this->price || 0.0 === $this->price) {
|
if (null === $this->price) {
|
||||||
return $label;
|
return $label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (0.0 === $this->price) {
|
||||||
|
return sprintf('%s (inkl.)', $label);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->price < 0) {
|
if ($this->price < 0) {
|
||||||
return sprintf('%s (-%s€ Rabatt)', $label, number_format(abs($this->price), 2, ',', '.'));
|
return sprintf('%s (-%s€ Rabatt)', $label, number_format(abs($this->price), 2, ',', '.'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,10 +102,14 @@ class BookingEditParticipantType extends AbstractType
|
|||||||
'choice_value' => 'id',
|
'choice_value' => 'id',
|
||||||
'choice_label' => function (?Service $service) use ($participantIndex) {
|
'choice_label' => function (?Service $service) use ($participantIndex) {
|
||||||
$price = $service->individualPrice[$participantIndex] ?? $service->price;
|
$price = $service->individualPrice[$participantIndex] ?? $service->price;
|
||||||
if (null === $price || 0.0 === $price) {
|
if (null === $price) {
|
||||||
return $service->label;
|
return $service->label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (0.0 === $price) {
|
||||||
|
return sprintf('%s (inkl.)', $service->label);
|
||||||
|
}
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'%s (%s €)',
|
'%s (%s €)',
|
||||||
$service->label,
|
$service->label,
|
||||||
@@ -256,10 +260,14 @@ class BookingEditParticipantType extends AbstractType
|
|||||||
|
|
||||||
$price = $pickup->price;
|
$price = $pickup->price;
|
||||||
|
|
||||||
if (null === $price || 0.0 === $price) {
|
if (null === $price) {
|
||||||
return $pickupLabel;
|
return $pickupLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (0.0 === $price) {
|
||||||
|
return sprintf('%s (inkl.)', $pickupLabel);
|
||||||
|
}
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'%s %s€',
|
'%s %s€',
|
||||||
$pickupLabel,
|
$pickupLabel,
|
||||||
|
|||||||
@@ -605,10 +605,14 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $service->price || 0.0 === $service->price) {
|
if (null === $service->price) {
|
||||||
return $service->label;
|
return $service->label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (0.0 === $service->price) {
|
||||||
|
return sprintf('%s (inkl.)', $service->label);
|
||||||
|
}
|
||||||
|
|
||||||
if ($service->price < 0) {
|
if ($service->price < 0) {
|
||||||
// Negative prices are discounts
|
// Negative prices are discounts
|
||||||
return sprintf('%s (-%s€ Rabatt)', $service->label, number_format(abs($service->price), 2, ',', '.'));
|
return sprintf('%s (-%s€ Rabatt)', $service->label, number_format(abs($service->price), 2, ',', '.'));
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class ParticipantCardDataService
|
|||||||
$name = trim($firstName.' '.$lastName);
|
$name = trim($firstName.' '.$lastName);
|
||||||
|
|
||||||
if ('' === $name) {
|
if ('' === $name) {
|
||||||
return 0 === $index ? 'Anmelder:in' : sprintf('Teilnehmer:in %d', $index);
|
return 0 === $index ? 'Anmelder:in' : 'Teilnehmer:in';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $name;
|
return $name;
|
||||||
|
|||||||
@@ -217,10 +217,6 @@ class ServicePricingCalculator
|
|||||||
$servicesBySubtypeAndSign = [];
|
$servicesBySubtypeAndSign = [];
|
||||||
|
|
||||||
foreach ($serviceAggregation as $serviceData) {
|
foreach ($serviceAggregation as $serviceData) {
|
||||||
if (0.0 === $serviceData['totalPrice']) {
|
|
||||||
continue; // Skip zero-price services
|
|
||||||
}
|
|
||||||
|
|
||||||
$subType = $serviceData['subType'] ?? 'other';
|
$subType = $serviceData['subType'] ?? 'other';
|
||||||
|
|
||||||
// Normalize rental subtypes to avoid duplicate sections
|
// Normalize rental subtypes to avoid duplicate sections
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class AppExtension extends AbstractExtension
|
|||||||
new TwigFilter('file_size', [AppRuntime::class, 'formatBytes']),
|
new TwigFilter('file_size', [AppRuntime::class, 'formatBytes']),
|
||||||
new TwigFilter('file_icon', [AppRuntime::class, 'fileIconFilter'], ['is_safe' => ['html']]),
|
new TwigFilter('file_icon', [AppRuntime::class, 'fileIconFilter'], ['is_safe' => ['html']]),
|
||||||
new TwigFilter('format_money', [AppRuntime::class, 'formatMoney']),
|
new TwigFilter('format_money', [AppRuntime::class, 'formatMoney']),
|
||||||
|
new TwigFilter('format_service_price', [AppRuntime::class, 'formatServicePrice']),
|
||||||
new TwigFilter('map_gender', [AppRuntime::class, 'mapGender']),
|
new TwigFilter('map_gender', [AppRuntime::class, 'mapGender']),
|
||||||
new TwigFilter('map_status', [AppRuntime::class, 'mapStatus']),
|
new TwigFilter('map_status', [AppRuntime::class, 'mapStatus']),
|
||||||
new TwigFilter('map_country', [AppRuntime::class, 'mapCountry']),
|
new TwigFilter('map_country', [AppRuntime::class, 'mapCountry']),
|
||||||
|
|||||||
@@ -52,6 +52,26 @@ class AppRuntime implements RuntimeExtensionInterface
|
|||||||
return $this->intlExtension->formatCurrency($amount, 'EUR');
|
return $this->intlExtension->formatCurrency($amount, 'EUR');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a service price, showing "inkl." for zero-priced (included) services.
|
||||||
|
*
|
||||||
|
* @param float|int|null $price The price to format
|
||||||
|
*
|
||||||
|
* @return string The formatted price or "inkl." for zero/null prices
|
||||||
|
*/
|
||||||
|
public function formatServicePrice(float|int|null $price): string
|
||||||
|
{
|
||||||
|
if (null === $price) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === $price || 0.0 === $price) {
|
||||||
|
return 'inkl.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->intlExtension->formatCurrency((float) $price, 'EUR');
|
||||||
|
}
|
||||||
|
|
||||||
public function mapStatus(string $status): string
|
public function mapStatus(string $status): string
|
||||||
{
|
{
|
||||||
$status = strtoupper($status);
|
$status = strtoupper($status);
|
||||||
|
|||||||
@@ -67,8 +67,8 @@
|
|||||||
</td>
|
</td>
|
||||||
{% if choiceData %}
|
{% if choiceData %}
|
||||||
<td class="border border-primary-bg p-2 align-top w-24 text-right whitespace-nowrap">
|
<td class="border border-primary-bg p-2 align-top w-24 text-right whitespace-nowrap">
|
||||||
{% if choiceData.price is defined and choiceData.price is not null %}
|
{% if choiceData.price is defined %}
|
||||||
{{ choiceData.price | format_currency('EUR') }}
|
{{ choiceData.price | format_service_price }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -51,9 +51,7 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</td>
|
</td>
|
||||||
<td class="border border-primary-bg p-2 align-top w-24 text-right whitespace-nowrap">
|
<td class="border border-primary-bg p-2 align-top w-24 text-right whitespace-nowrap">
|
||||||
{% if price is not null %}
|
{{ price | format_service_price }}
|
||||||
{{ price | format_currency('EUR') }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="border border-primary-bg p-2 align-top w-12 text-center">
|
<td class="border border-primary-bg p-2 align-top w-12 text-center">
|
||||||
{% set childTooltip = child.vars.attr['data-tooltip']|default(null) %}
|
{% set childTooltip = child.vars.attr['data-tooltip']|default(null) %}
|
||||||
@@ -84,9 +82,7 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</td>
|
</td>
|
||||||
<td class="border border-primary-bg p-2 align-top w-24 text-right whitespace-nowrap">
|
<td class="border border-primary-bg p-2 align-top w-24 text-right whitespace-nowrap">
|
||||||
{% if price is not null %}
|
{{ price | format_service_price }}
|
||||||
{{ price | format_currency('EUR') }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="border border-primary-bg p-2 align-top w-12 text-center">
|
<td class="border border-primary-bg p-2 align-top w-12 text-center">
|
||||||
{% set fieldTooltip = checkboxField.vars.attr['data-tooltip']|default(null) %}
|
{% set fieldTooltip = checkboxField.vars.attr['data-tooltip']|default(null) %}
|
||||||
@@ -124,9 +120,7 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</td>
|
</td>
|
||||||
<td class="border border-primary-bg p-2 align-top w-24 text-right whitespace-nowrap">
|
<td class="border border-primary-bg p-2 align-top w-24 text-right whitespace-nowrap">
|
||||||
{% if price is not null %}
|
{{ price | format_service_price }}
|
||||||
{{ price | format_currency('EUR') }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="border border-primary-bg p-2 align-top w-12 text-center">
|
<td class="border border-primary-bg p-2 align-top w-12 text-center">
|
||||||
{% set childTooltip = child.vars.attr['data-tooltip']|default(null) %}
|
{% set childTooltip = child.vars.attr['data-tooltip']|default(null) %}
|
||||||
@@ -364,11 +358,11 @@
|
|||||||
) }}
|
) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
{% elseif form.skiPass is defined %}
|
{% elseif form.skiPass is defined and not form.vars.data.participant.skiPass %}
|
||||||
{# Skipass field exists but no rentals yet - user needs to select skipass first #}
|
{# Skipass field exists but no skipass selected yet #}
|
||||||
<div class="p-2 text-sm text-gray-500">Bitte zuerst den Skipass auswählen</div>
|
<div class="p-2 text-sm text-gray-500">Bitte zuerst den Skipass auswählen</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{# No skipass field (e.g., baby participant) - rentals not available #}
|
{# No skipass field or skipass selected but no matching rentals #}
|
||||||
<div class="p-2 text-sm text-gray-500">Nicht wählbar</div>
|
<div class="p-2 text-sm text-gray-500">Nicht wählbar</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -146,7 +146,7 @@
|
|||||||
{{ servicePricing.participantCount }}x {{ servicePricing.label }}
|
{{ servicePricing.participantCount }}x {{ servicePricing.label }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
|
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
|
||||||
{{ servicePricing.totalPrice | format_currency('EUR') }}
|
{{ servicePricing.totalPrice | format_service_price }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ class ParticipantCardDataServiceTest extends TestCase
|
|||||||
$this->assertEquals('500,00 €', $result[1]['price']);
|
$this->assertEquals('500,00 €', $result[1]['price']);
|
||||||
|
|
||||||
// Third participant (no name)
|
// Third participant (no name)
|
||||||
$this->assertEquals('Teilnehmer:in 2', $result[2]['name']);
|
$this->assertEquals('Teilnehmer:in', $result[2]['name']);
|
||||||
$this->assertEquals('Doppelzimmer', $result[2]['roomName']);
|
$this->assertEquals('Doppelzimmer', $result[2]['roomName']);
|
||||||
$this->assertEquals('480,00 €', $result[2]['price']);
|
$this->assertEquals('480,00 €', $result[2]['price']);
|
||||||
}
|
}
|
||||||
@@ -347,8 +347,8 @@ class ParticipantCardDataServiceTest extends TestCase
|
|||||||
$result3 = $this->service->getCardData($bookingDto, 2);
|
$result3 = $this->service->getCardData($bookingDto, 2);
|
||||||
|
|
||||||
$this->assertEquals('Anmelder:in', $result1['name']);
|
$this->assertEquals('Anmelder:in', $result1['name']);
|
||||||
$this->assertEquals('Teilnehmer:in 1', $result2['name']);
|
$this->assertEquals('Teilnehmer:in', $result2['name']);
|
||||||
$this->assertEquals('Teilnehmer:in 2', $result3['name']);
|
$this->assertEquals('Teilnehmer:in', $result3['name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetCardDataWithValidationReturnsValidCard(): void
|
public function testGetCardDataWithValidationReturnsValidCard(): void
|
||||||
|
|||||||
Reference in New Issue
Block a user