feat: additional age groups in price calculator

closes #86997e7e9, #869979zhq
This commit is contained in:
Björn Fromme
2025-06-02 11:26:13 +02:00
parent 5b90f82746
commit 67f2f1a5fa
9 changed files with 229 additions and 70 deletions
@@ -28,6 +28,7 @@ namespace EP\EpProducts\Controller;
use EP\EpProducts\Domain\Model\Dto\GroupsPriceInquiry; use EP\EpProducts\Domain\Model\Dto\GroupsPriceInquiry;
use EP\EpProducts\Domain\Model\GroupsPriceBoard; use EP\EpProducts\Domain\Model\GroupsPriceBoard;
use EP\EpProducts\Domain\Model\GroupsPriceOption;
use EP\EpProducts\Domain\Model\Hotel; use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Repository\GroupsPriceOptionRepository; use EP\EpProducts\Domain\Repository\GroupsPriceOptionRepository;
use EP\EpProducts\Service\EmailService; use EP\EpProducts\Service\EmailService;
@@ -69,6 +70,9 @@ class AjaxGroupsPriceController extends ActionController
public function processFormAction(Hotel $hotel, GroupsPriceInquiry $groupsPriceInquiry) public function processFormAction(Hotel $hotel, GroupsPriceInquiry $groupsPriceInquiry)
{ {
$mode = $groupsPriceInquiry->getMode(); $mode = $groupsPriceInquiry->getMode();
$country = $hotel->getCountry()->getCode();
$effectivePax = $groupsPriceInquiry->getEffectivePax();
$nights = $groupsPriceInquiry->getNights();
if (false === in_array($mode, [GroupsPriceInquiry::MODE_BOOKING, GroupsPriceInquiry::MODE_INQUIRY])) { if (false === in_array($mode, [GroupsPriceInquiry::MODE_BOOKING, GroupsPriceInquiry::MODE_INQUIRY])) {
$mode = GroupsPriceInquiry::MODE_BOOKING; $mode = GroupsPriceInquiry::MODE_BOOKING;
@@ -86,10 +90,25 @@ class AjaxGroupsPriceController extends ActionController
'templateName' => 'GroupsPrice', 'templateName' => 'GroupsPrice',
]; ];
$optionPrice = function (GroupsPriceOption $option) use ($country, $effectivePax, $nights) {
$price = 'CH' === $country ? $option->getPriceChf() : $option->getPrice();
$currency = 'CH' === $country ? 'CHF' : '€';
if (GroupsPriceOption::TYPE_PER_PAX === $option->getType()) {
$price = $price * $effectivePax;
} elseif (GroupsPriceOption::TYPE_PER_NIGHT === $option->getType()) {
$price = $price * $nights;
} elseif (GroupsPriceOption::TYPE_PER_PAX_AND_NIGHT === $option->getType()) {
$price = $price * $nights * $effectivePax;
}
return $price.' '.$currency;
};
try { try {
$options = $this->groupsPriceOptionRepository->findByUids($groupsPriceInquiry->getOptions()); $options = $this->groupsPriceOptionRepository->findByUids($groupsPriceInquiry->getOptions());
$selectedOptions = array_map(function ($option) { $selectedOptions = array_map(function ($option) use ($optionPrice) {
return $option->getTitle(); return $option->getTitle().': '.$optionPrice($option);
}, $options); }, $options);
} }
catch (InvalidQueryException $e) { catch (InvalidQueryException $e) {
@@ -111,6 +130,10 @@ class AjaxGroupsPriceController extends ActionController
'dateTo' => $groupsPriceInquiry->getDateTo(), 'dateTo' => $groupsPriceInquiry->getDateTo(),
'nights' => $groupsPriceInquiry->getNights(), 'nights' => $groupsPriceInquiry->getNights(),
'pax' => $groupsPriceInquiry->getPax(), 'pax' => $groupsPriceInquiry->getPax(),
'children' => $groupsPriceInquiry->getChildren(),
'minors' => $groupsPriceInquiry->getMinors(),
'adolescents' => $groupsPriceInquiry->getAdolescents(),
'adolescentsAge' => $hotel->getGroupsPriceAdolescentsAge(),
'options' => $selectedOptions, 'options' => $selectedOptions,
'board' => $groupsPriceInquiry->getBoard(), 'board' => $groupsPriceInquiry->getBoard(),
'summary' => $groupsPriceInquiry->getSummary(), 'summary' => $groupsPriceInquiry->getSummary(),
@@ -62,5 +62,6 @@ class GroupsPriceController extends ActionController
$this->view->assign('options', json_encode($hotel->getGroupsPriceOptions()->toArray(), JSON_HEX_APOS)); $this->view->assign('options', json_encode($hotel->getGroupsPriceOptions()->toArray(), JSON_HEX_APOS));
$this->view->assign('mainSeasonFrom', $mainSeasonFrom); $this->view->assign('mainSeasonFrom', $mainSeasonFrom);
$this->view->assign('mainSeasonTo', $mainSeasonTo); $this->view->assign('mainSeasonTo', $mainSeasonTo);
$this->view->assign('adolescentsAge', $hotel->getGroupsPriceAdolescentsAge() ?? 0);
} }
} }
@@ -119,6 +119,16 @@ class GroupsPriceInquiry
*/ */
protected $children; protected $children;
/**
* @var string
*/
protected $minors;
/**
* @var string
*/
protected $adolescents;
/** /**
* @var array * @var array
*/ */
@@ -384,6 +394,46 @@ class GroupsPriceInquiry
$this->children = $children; $this->children = $children;
} }
/**
* @return string
*/
public function getMinors()
{
return $this->minors;
}
/**
* @param string $minors
*/
public function setMinors($minors)
{
$this->minors = $minors;
}
/**
* @return string
*/
public function getAdolescents()
{
return $this->adolescents;
}
/**
* @param string $adolescents
*/
public function setAdolescents($adolescents)
{
$this->adolescents = $adolescents;
}
/**
* @return int
*/
public function getEffectivePax()
{
return max(30, $this->getPax() - $this->getChildren());
}
/** /**
* @return array * @return array
*/ */
@@ -265,6 +265,11 @@ class Hotel extends AbstractEntity implements TeaserInterface
*/ */
protected $groupsPriceBoards; protected $groupsPriceBoards;
/**
* @var int
*/
protected $groupsPriceAdolescentsAge;
/** /**
* @var string * @var string
*/ */
@@ -1059,6 +1064,16 @@ class Hotel extends AbstractEntity implements TeaserInterface
$this->groupsPriceBoards = $groupsPriceBoards; $this->groupsPriceBoards = $groupsPriceBoards;
} }
public function getGroupsPriceAdolescentsAge()
{
return $this->groupsPriceAdolescentsAge;
}
public function setGroupsPriceAdolescentsAge($groupsPriceAdolescentsAge)
{
$this->groupsPriceAdolescentsAge = $groupsPriceAdolescentsAge;
}
public function getGroupsPriceTaxLabel() public function getGroupsPriceTaxLabel()
{ {
return $this->groupsPriceTaxLabel; return $this->groupsPriceTaxLabel;
@@ -30,7 +30,8 @@ return [
--div--;Texte, teaser, description, features, room_types, additional_information, --div--;Texte, teaser, description, features, room_types, additional_information,
--div--;Verknüpfungen, facts, teamer, additional_codes, --div--;Verknüpfungen, facts, teamer, additional_codes,
--div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images, --div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images,
--div--;Gruppenhaus Preise, --palette--;;season,groups_price_tax_label, groups_price_configs, groups_price_boards, groups_price_options', --div--;Gruppenhaus Preise, --palette--;;season,--palette--;;others, groups_price_configs,
groups_price_boards, groups_price_options',
], ],
], ],
'palettes' => [ 'palettes' => [
@@ -41,6 +42,7 @@ return [
'name' => ['showitem' => 'name,short_name,--linebreak--,header_title,header_subtitle,--linebreak--,headline'], 'name' => ['showitem' => 'name,short_name,--linebreak--,header_title,header_subtitle,--linebreak--,headline'],
'categories' => ['showitem' => 'type,category'], 'categories' => ['showitem' => 'type,category'],
'season' => ['showitem' => 'main_season_from, main_season_to'], 'season' => ['showitem' => 'main_season_from, main_season_to'],
'others' => ['showitem' => 'season,groups_price_tax_label, groups_price_adolescents_age']
], ],
'columns' => [ 'columns' => [
@@ -702,6 +704,15 @@ return [
'default' => 'zzgl. vor Ort zu entrichtender Ortstaxe', 'default' => 'zzgl. vor Ort zu entrichtender Ortstaxe',
], ],
], ],
'groups_price_adolescents_age' => [
'exclude' => 0,
'label' => 'Altersgrenze Jugendliche (6-X Jahre)',
'config' => [
'type' => 'input',
'size' => 50,
'eval' => 'int',
],
],
'main_season_from' => [ 'main_season_from' => [
'exclude' => false, 'exclude' => false,
'label' => 'Hauptsaison von', 'label' => 'Hauptsaison von',
@@ -255,6 +255,7 @@ CREATE TABLE tx_epproducts_domain_model_hotel
groups_price_configs int(11) unsigned NOT NULL default '0', groups_price_configs int(11) unsigned NOT NULL default '0',
groups_price_options int(11) unsigned NOT NULL default '0', groups_price_options int(11) unsigned NOT NULL default '0',
groups_price_boards int(11) unsigned NOT NULL default '0', groups_price_boards int(11) unsigned NOT NULL default '0',
groups_price_adolescents_age int(11) unsigned NOT NULL default '0',
groups_price_tax_label varchar(255) DEFAULT '' NOT NULL, groups_price_tax_label varchar(255) DEFAULT '' NOT NULL,
main_season_from date DEFAULT NULL, main_season_from date DEFAULT NULL,
main_season_to date DEFAULT NULL, main_season_to date DEFAULT NULL,
@@ -43,9 +43,48 @@
v-model.number="childrenCount" v-model.number="childrenCount"
@change="onPaxUpdated()" @change="onPaxUpdated()"
v-show="nightsCount > 0"/> v-show="nightsCount > 0"/>
<p class="text-sm italic"> <p class="text-sm italic"
Kinder werden nur bei Strom- und Abfallgebühren berücksichtigt v-show="nightsCount === 0">
Bitte zuerst einen Zeitraum wählen
</p> </p>
<p class="text-sm italic"
v-show="nightsCount > 0">
Kinder werden nur bei Strom- und Abfallgebühren berücksichtigt.
</p>
</div>
<div>
<label class="mb-2 font-bold">
davon Kinder 3-5 Jahre*
</label>
<input type="number"
class="form-field"
min="0"
v-model.number="childrenCount3to5"
@change="onPaxUpdated()"
v-show="nightsCount > 0"/>
<p class="text-sm italic"
v-show="nightsCount === 0">
Bitte zuerst einen Zeitraum wählen
</p>
</div>
<div v-if="adolescentsAge > 0">
<label class="mb-2 font-bold">
davon Kinder 6-{{ adolescentsAge }} Jahre*
</label>
<input type="number"
class="form-field"
min="0"
v-model.number="adolescentsCount"
@change="onPaxUpdated()"
v-show="nightsCount > 0"/>
<p class="text-sm italic"
v-show="nightsCount === 0">
Bitte zuerst einen Zeitraum wählen
</p>
</div>
<div class="sm:col-span-2 text-sm">
*Die Kosten für Essen und Kurtaxe werden wir entsprechend der Altersstruktur der Gäste in der Rechnung
anpassen.
</div> </div>
</div> </div>
<div class="mb-8" <div class="mb-8"
@@ -395,6 +434,10 @@ export default {
type: String, type: String,
default: 'zzgl. vor Ort zu entrichtender Ortstaxe', default: 'zzgl. vor Ort zu entrichtender Ortstaxe',
}, },
adolescentsAge: {
type: Number,
default: 0,
}
}, },
data() { data() {
return { return {
@@ -410,6 +453,8 @@ export default {
selectedRange: [], selectedRange: [],
selectedPax: 30, selectedPax: 30,
childrenCount: 0, childrenCount: 0,
childrenCount3to5: 0,
adolescentsCount: 0,
selectedOptions: [], selectedOptions: [],
selectedBoard: null, selectedBoard: null,
name: '', name: '',
@@ -533,6 +578,8 @@ export default {
'tx_epproducts_ajax[groupsPriceInquiry][nights]': this.nightsCount, 'tx_epproducts_ajax[groupsPriceInquiry][nights]': this.nightsCount,
'tx_epproducts_ajax[groupsPriceInquiry][pax]': this.selectedPax, 'tx_epproducts_ajax[groupsPriceInquiry][pax]': this.selectedPax,
'tx_epproducts_ajax[groupsPriceInquiry][children]': this.childrenCount, 'tx_epproducts_ajax[groupsPriceInquiry][children]': this.childrenCount,
'tx_epproducts_ajax[groupsPriceInquiry][minors]': this.childrenCount3to5,
'tx_epproducts_ajax[groupsPriceInquiry][adolescents]': this.adolescentsCount,
'tx_epproducts_ajax[groupsPriceInquiry][summary][paxBase]': this.formatCurrency(this.pricePax.base), 'tx_epproducts_ajax[groupsPriceInquiry][summary][paxBase]': this.formatCurrency(this.pricePax.base),
'tx_epproducts_ajax[groupsPriceInquiry][summary][paxAdditional]': this.formatCurrency(this.pricePax.additional), 'tx_epproducts_ajax[groupsPriceInquiry][summary][paxAdditional]': this.formatCurrency(this.pricePax.additional),
'tx_epproducts_ajax[groupsPriceInquiry][summary][undersubscription]': this.formatCurrency(this.pricePax.undersubscription), 'tx_epproducts_ajax[groupsPriceInquiry][summary][undersubscription]': this.formatCurrency(this.pricePax.undersubscription),
@@ -616,8 +663,8 @@ export default {
base.EUR += config.price.EUR base.EUR += config.price.EUR
base.CHF += config.price.CHF base.CHF += config.price.CHF
let included = config.personsIncluded let included = config.personsIncluded
if (this.paxCount > included) {
let additionalPax = this.paxCount - included let additionalPax = this.paxCount - included
if (this.selectedPax > included) {
additional.EUR += additionalPax * config.priceAdditionalPerson.EUR additional.EUR += additionalPax * config.priceAdditionalPerson.EUR
additional.CHF += additionalPax * config.priceAdditionalPerson.CHF additional.CHF += additionalPax * config.priceAdditionalPerson.CHF
} }
@@ -70,6 +70,16 @@
<th>Anzahl Kinder 0-3 Jahre</th> <th>Anzahl Kinder 0-3 Jahre</th>
<td>{children -> v:or(alternative: '-')}</td> <td>{children -> v:or(alternative: '-')}</td>
</tr> </tr>
<tr>
<th>Anzahl Kinder 3-5 Jahre</th>
<td>{minors -> v:or(alternative: '-')}</td>
</tr>
<f:if condition="{adolescentsAge}">
<tr>
<th>Anzahl Kinder 6-{adolescentsAge} Jahre</th>
<td>{adolescents -> v:or(alternative: '-')}</td>
</tr>
</f:if>
<tr> <tr>
<th>Verpflegung</th> <th>Verpflegung</th>
<td> <td>
@@ -27,6 +27,7 @@
main-season-from="{mainSeasonFrom}" main-season-from="{mainSeasonFrom}"
main-season-to="{mainSeasonTo}" main-season-to="{mainSeasonTo}"
tax-label="{hotel.groupsPriceTaxLabel -> v:or(alternative: 'zzgl. vor Ort zu entrichtender Ortstaxe')}" tax-label="{hotel.groupsPriceTaxLabel -> v:or(alternative: 'zzgl. vor Ort zu entrichtender Ortstaxe')}"
adolescents-age="{adolescentsAge}"
> >
</groups-price-calculator> </groups-price-calculator>
</f:format.raw> </f:format.raw>