Merge branch 'master' into develop

This commit is contained in:
Björn Fromme
2025-06-02 13:30:58 +02:00
21 changed files with 527 additions and 156 deletions
@@ -28,6 +28,7 @@ namespace EP\EpProducts\Controller;
use EP\EpProducts\Domain\Model\Dto\GroupsPriceInquiry;
use EP\EpProducts\Domain\Model\GroupsPriceBoard;
use EP\EpProducts\Domain\Model\GroupsPriceOption;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Repository\GroupsPriceOptionRepository;
use EP\EpProducts\Service\EmailService;
@@ -69,6 +70,9 @@ class AjaxGroupsPriceController extends ActionController
public function processFormAction(Hotel $hotel, GroupsPriceInquiry $groupsPriceInquiry)
{
$mode = $groupsPriceInquiry->getMode();
$country = $hotel->getCountry()->getCode();
$effectivePax = $groupsPriceInquiry->getEffectivePax();
$nights = $groupsPriceInquiry->getNights();
if (false === in_array($mode, [GroupsPriceInquiry::MODE_BOOKING, GroupsPriceInquiry::MODE_INQUIRY])) {
$mode = GroupsPriceInquiry::MODE_BOOKING;
@@ -86,10 +90,25 @@ class AjaxGroupsPriceController extends ActionController
'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 {
$options = $this->groupsPriceOptionRepository->findByUids($groupsPriceInquiry->getOptions());
$selectedOptions = array_map(function ($option) {
return $option->getTitle();
$selectedOptions = array_map(function ($option) use ($optionPrice) {
return $option->getTitle().': '.$optionPrice($option);
}, $options);
}
catch (InvalidQueryException $e) {
@@ -101,13 +120,20 @@ class AjaxGroupsPriceController extends ActionController
'hotel' => $hotel,
'name' => $groupsPriceInquiry->getName(),
'group' => $groupsPriceInquiry->getGroup(),
'address' => $groupsPriceInquiry->getAddress(),
'street' => $groupsPriceInquiry->getStreet(),
'postcode' => $groupsPriceInquiry->getPostcode(),
'city' => $groupsPriceInquiry->getCity(),
'email' => $groupsPriceInquiry->getEmail(),
'phone' => $groupsPriceInquiry->getPhone(),
'remarks' => $groupsPriceInquiry->getRemarks(),
'dateFrom' => $groupsPriceInquiry->getDateFrom(),
'dateTo' => $groupsPriceInquiry->getDateTo(),
'nights' => $groupsPriceInquiry->getNights(),
'pax' => $groupsPriceInquiry->getPax(),
'children' => $groupsPriceInquiry->getChildren(),
'minors' => $groupsPriceInquiry->getMinors(),
'adolescents' => $groupsPriceInquiry->getAdolescents(),
'adolescentsAge' => $hotel->getGroupsPriceAdolescentsAge(),
'options' => $selectedOptions,
'board' => $groupsPriceInquiry->getBoard(),
'summary' => $groupsPriceInquiry->getSummary(),
@@ -124,8 +150,7 @@ class AjaxGroupsPriceController extends ActionController
protected function errorAction() {
$formErrors = [];
if ($this->arguments->validate()->hasErrors()) {
foreach ($this->arguments->validate()->getFlattenedErrors() as $key => $errors)
{
foreach ($this->arguments->validate()->getFlattenedErrors() as $key => $errors) {
$parts = explode('.', $key);
$fieldName = array_pop($parts);
$errorsRaw = [];
@@ -62,5 +62,6 @@ class GroupsPriceController extends ActionController
$this->view->assign('options', json_encode($hotel->getGroupsPriceOptions()->toArray(), JSON_HEX_APOS));
$this->view->assign('mainSeasonFrom', $mainSeasonFrom);
$this->view->assign('mainSeasonTo', $mainSeasonTo);
$this->view->assign('adolescentsAge', $hotel->getGroupsPriceAdolescentsAge() ?? 0);
}
}
@@ -60,7 +60,19 @@ class GroupsPriceInquiry
* @var string
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $address;
protected $street;
/**
* @var string
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $postcode;
/**
* @var string
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $city;
/**
* @var string
@@ -92,6 +104,11 @@ class GroupsPriceInquiry
*/
protected $dateTo;
/**
* @var string
*/
protected $nights;
/**
* @var string
*/
@@ -102,6 +119,16 @@ class GroupsPriceInquiry
*/
protected $children;
/**
* @var string
*/
protected $minors;
/**
* @var string
*/
protected $adolescents;
/**
* @var array
*/
@@ -109,6 +136,7 @@ class GroupsPriceInquiry
/**
* @var GroupsPriceBoard
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $board;
@@ -191,21 +219,53 @@ class GroupsPriceInquiry
/**
* @return string
*/
public function getAddress()
public function getStreet()
{
return $this->address;
return $this->street;
}
/**
* @param string $address
* @param string $street
*/
public function setAddress($address)
public function setStreet($street)
{
$this->address = $address;
$this->street = $street;
return $this;
}
/**
* @return string
*/
public function getPostcode()
{
return $this->postcode;
}
/**
* @param string $postcode
*/
public function setPostcode($postcode)
{
$this->postcode = $postcode;
}
/**
* @return string
*/
public function getCity()
{
return $this->city;
}
/**
* @param string $city
*/
public function setCity($city)
{
$this->city = $city;
}
/**
* @return string
*/
@@ -286,6 +346,22 @@ class GroupsPriceInquiry
$this->dateTo = $dateTo;
}
/**
* @return string
*/
public function getNights()
{
return $this->nights;
}
/**
* @param string $nights
*/
public function setNights($nights)
{
$this->nights = $nights;
}
/**
* @return string
*/
@@ -318,6 +394,46 @@ class GroupsPriceInquiry
$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
*/
@@ -265,6 +265,16 @@ class Hotel extends AbstractEntity implements TeaserInterface
*/
protected $groupsPriceBoards;
/**
* @var int
*/
protected $groupsPriceAdolescentsAge;
/**
* @var string
*/
protected $groupsPriceTaxLabel;
/**
* @var \DateTime
*/
@@ -1054,6 +1064,26 @@ class Hotel extends AbstractEntity implements TeaserInterface
$this->groupsPriceBoards = $groupsPriceBoards;
}
public function getGroupsPriceAdolescentsAge()
{
return $this->groupsPriceAdolescentsAge;
}
public function setGroupsPriceAdolescentsAge($groupsPriceAdolescentsAge)
{
$this->groupsPriceAdolescentsAge = $groupsPriceAdolescentsAge;
}
public function getGroupsPriceTaxLabel()
{
return $this->groupsPriceTaxLabel;
}
public function setGroupsPriceTaxLabel($groupsPriceTaxLabel)
{
$this->groupsPriceTaxLabel = $groupsPriceTaxLabel;
}
public function getMainSeasonFrom()
{
return $this->mainSeasonFrom;
@@ -358,7 +358,8 @@ class ProductRepository extends AbstractRepository
'date.bus_pro_id as dateBusProId', 'date.hotel_bus_pro_id as hotelBusProId',
'date.date_end as dateEnd', 'date.hotel as hotelUid', 'date.hotel_name as hotelName',
'date.hotel_header_title as hotelTitle', 'date.hotel_category as hotelCategory',
'room.pax as roomPax', 'room.price as roomPrice', 'room.available as available',
'room.name as roomName', 'room.pax as roomPax', 'room.price as roomPrice',
'room.available as available',
'hotel.detail_page as hotelDetailPageUid', 'hotel.short_name as hotelShortName',
'hotel.show_as_teaser as hotelShowAsTeaser'
)
@@ -384,6 +384,8 @@ class DateService implements SingletonInterface
}
$entry =& $priceTable[$categoryUid][$hotelUid]['rooms'];
$isUndersubscription = 1 === preg_match('/mit \d{1,2} Personen/', $row['roomName']);
// Add new entry or replace possible existing entry that is not
// bookable with current record in case of matching category
// and pax
@@ -391,7 +393,7 @@ class DateService implements SingletonInterface
$entry[$roomPax] = [
'price' => $row['roomPrice'],
'available' => $row['available'] && $row['roomPrice'],
'undersubscription' => $isUndersubscription,
] ;
} else {
$lastEntry = $entry[$roomPax];
@@ -399,6 +401,7 @@ class DateService implements SingletonInterface
$entry[$roomPax] = [
'price' => $row['roomPrice'],
'available' => $row['available'] && $row['roomPrice'],
'undersubscription' => $isUndersubscription,
];
}
}
@@ -44,6 +44,7 @@ return [
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1],
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0]
],
'default' => -1,
],
],
'l10n_parent' => [
@@ -45,6 +45,7 @@ return [
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1],
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0]
],
'default' => -1,
],
],
'l10n_parent' => [
@@ -30,7 +30,8 @@ return [
--div--;Texte, teaser, description, features, room_types, additional_information,
--div--;Verknüpfungen, facts, teamer, additional_codes,
--div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images,
--div--;Gruppenhaus Preise, --palette--;;season, 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' => [
@@ -41,6 +42,7 @@ return [
'name' => ['showitem' => 'name,short_name,--linebreak--,header_title,header_subtitle,--linebreak--,headline'],
'categories' => ['showitem' => 'type,category'],
'season' => ['showitem' => 'main_season_from, main_season_to'],
'others' => ['showitem' => 'season,groups_price_tax_label, groups_price_adolescents_age']
],
'columns' => [
@@ -658,6 +660,7 @@ return [
'type' => 'inline',
'foreign_table' => 'tx_epproducts_domain_model_groupspriceboard',
'foreign_field' => 'hotel',
'foreign_table_where' => 'ORDER BY price ASC',
'maxitems' => 99,
'minitems' => 0,
'appearance' => [
@@ -691,6 +694,25 @@ return [
],
],
],
'groups_price_tax_label' => [
'exclude' => 0,
'label' => 'Text Ortstaxe',
'config' => [
'type' => 'input',
'size' => 50,
'eval' => 'trim',
'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' => [
'exclude' => false,
'label' => 'Hauptsaison von',
+64 -62
View File
@@ -213,72 +213,74 @@ CREATE TABLE tx_epproducts_domain_model_date
CREATE TABLE tx_epproducts_domain_model_hotel
(
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
earlybird tinyint(4) unsigned DEFAULT '0' NOT NULL,
new tinyint(4) unsigned DEFAULT '0' NOT NULL,
low_contingent tinyint(4) unsigned DEFAULT '0' NOT NULL,
show_as_teaser tinyint(4) unsigned DEFAULT '0' NOT NULL,
detail_page varchar(255) DEFAULT '' NOT NULL,
name varchar(255) DEFAULT '' NOT NULL,
short_name varchar(255) DEFAULT '' NOT NULL,
headline varchar(255) DEFAULT '' NOT NULL,
description text NOT NULL,
keywords text NOT NULL,
features text NOT NULL,
room_types text NOT NULL,
additional_information text NOT NULL,
teaser text NOT NULL,
header_title varchar(255) DEFAULT '' NOT NULL,
header_subtitle varchar(255) DEFAULT '' NOT NULL,
teaser_images int(11) unsigned NOT NULL default '0',
header_images int(11) unsigned NOT NULL default '0',
images int(11) unsigned NOT NULL default '0',
reseller_images int(11) unsigned NOT NULL default '0',
code varchar(255) DEFAULT '' NOT NULL,
external_link varchar(255) DEFAULT '' NOT NULL,
path_segment varchar(255) DEFAULT '' NOT NULL,
type varchar(255) DEFAULT '' NOT NULL,
category int(11) DEFAULT '0' NOT NULL,
country int(11) unsigned DEFAULT '0',
region int(11) unsigned DEFAULT '0',
city int(11) unsigned DEFAULT '0',
teamer int(11) unsigned DEFAULT '0' NOT NULL,
products int(11) unsigned DEFAULT '0' NOT NULL,
rooms int(11) unsigned DEFAULT '0' NOT NULL,
facts int(11) unsigned DEFAULT '0' NOT NULL,
additional_codes int(11) unsigned DEFAULT '0' NOT NULL,
address varchar(255) DEFAULT '' NOT NULL,
latitude varchar(255) DEFAULT '' NOT NULL,
longitude varchar(255) DEFAULT '' NOT NULL,
groups_price_configs 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',
main_season_from date DEFAULT NULL,
main_season_to date DEFAULT NULL,
earlybird tinyint(4) unsigned DEFAULT '0' NOT NULL,
new tinyint(4) unsigned DEFAULT '0' NOT NULL,
low_contingent tinyint(4) unsigned DEFAULT '0' NOT NULL,
show_as_teaser tinyint(4) unsigned DEFAULT '0' NOT NULL,
detail_page varchar(255) DEFAULT '' NOT NULL,
name varchar(255) DEFAULT '' NOT NULL,
short_name varchar(255) DEFAULT '' NOT NULL,
headline varchar(255) DEFAULT '' NOT NULL,
description text NOT NULL,
keywords text NOT NULL,
features text NOT NULL,
room_types text NOT NULL,
additional_information text NOT NULL,
teaser text NOT NULL,
header_title varchar(255) DEFAULT '' NOT NULL,
header_subtitle varchar(255) DEFAULT '' NOT NULL,
teaser_images int(11) unsigned NOT NULL default '0',
header_images int(11) unsigned NOT NULL default '0',
images int(11) unsigned NOT NULL default '0',
reseller_images int(11) unsigned NOT NULL default '0',
code varchar(255) DEFAULT '' NOT NULL,
external_link varchar(255) DEFAULT '' NOT NULL,
path_segment varchar(255) DEFAULT '' NOT NULL,
type varchar(255) DEFAULT '' NOT NULL,
category int(11) DEFAULT '0' NOT NULL,
country int(11) unsigned DEFAULT '0',
region int(11) unsigned DEFAULT '0',
city int(11) unsigned DEFAULT '0',
teamer int(11) unsigned DEFAULT '0' NOT NULL,
products int(11) unsigned DEFAULT '0' NOT NULL,
rooms int(11) unsigned DEFAULT '0' NOT NULL,
facts int(11) unsigned DEFAULT '0' NOT NULL,
additional_codes int(11) unsigned DEFAULT '0' NOT NULL,
address varchar(255) DEFAULT '' NOT NULL,
latitude varchar(255) DEFAULT '' NOT NULL,
longitude varchar(255) DEFAULT '' NOT NULL,
groups_price_configs 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_adolescents_age int(11) unsigned NOT NULL default '0',
groups_price_tax_label varchar(255) DEFAULT '' NOT NULL,
main_season_from date DEFAULT NULL,
main_season_to date DEFAULT NULL,
tstamp int(11) unsigned DEFAULT '0' NOT NULL,
crdate int(11) unsigned DEFAULT '0' NOT NULL,
cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
deleted tinyint(4) unsigned DEFAULT '0' NOT NULL,
hidden tinyint(4) unsigned DEFAULT '0' NOT NULL,
starttime int(11) unsigned DEFAULT '0' NOT NULL,
endtime int(11) unsigned DEFAULT '0' NOT NULL,
tstamp int(11) unsigned DEFAULT '0' NOT NULL,
crdate int(11) unsigned DEFAULT '0' NOT NULL,
cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
deleted tinyint(4) unsigned DEFAULT '0' NOT NULL,
hidden tinyint(4) unsigned DEFAULT '0' NOT NULL,
starttime int(11) unsigned DEFAULT '0' NOT NULL,
endtime int(11) unsigned DEFAULT '0' NOT NULL,
t3ver_oid int(11) DEFAULT '0' NOT NULL,
t3ver_id int(11) DEFAULT '0' NOT NULL,
t3ver_wsid int(11) DEFAULT '0' NOT NULL,
t3ver_label varchar(255) DEFAULT '' NOT NULL,
t3ver_state tinyint(4) DEFAULT '0' NOT NULL,
t3ver_stage int(11) DEFAULT '0' NOT NULL,
t3ver_count int(11) DEFAULT '0' NOT NULL,
t3ver_tstamp int(11) DEFAULT '0' NOT NULL,
t3ver_move_id int(11) DEFAULT '0' NOT NULL,
t3ver_oid int(11) DEFAULT '0' NOT NULL,
t3ver_id int(11) DEFAULT '0' NOT NULL,
t3ver_wsid int(11) DEFAULT '0' NOT NULL,
t3ver_label varchar(255) DEFAULT '' NOT NULL,
t3ver_state tinyint(4) DEFAULT '0' NOT NULL,
t3ver_stage int(11) DEFAULT '0' NOT NULL,
t3ver_count int(11) DEFAULT '0' NOT NULL,
t3ver_tstamp int(11) DEFAULT '0' NOT NULL,
t3ver_move_id int(11) DEFAULT '0' NOT NULL,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
PRIMARY KEY (uid),
KEY parent (pid),
@@ -43,9 +43,48 @@
v-model.number="childrenCount"
@change="onPaxUpdated()"
v-show="nightsCount > 0"/>
<p class="text-sm italic">
Kinder werden nur bei Strom- und Abfallgebühren berücksichtigt
<p class="text-sm italic"
v-show="nightsCount === 0">
Bitte zuerst einen Zeitraum wählen
</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 class="mb-8"
@@ -68,18 +107,21 @@
<div class="mb-8"
v-show="boards.length > 0 && !submitted">
<label class="mb-2 font-bold">
Optionale Verpflegungsleistungen
Verpflegungsleistungen
</label>
<select class="form-field"
v-model="selectedBoard"
v-if="selectedPax >= 30">
<option :value="null">Keine Auswahl</option>
<option :value="null" disabled>bitte auswählen</option>
<option v-for="board of boards"
:value="board"
:key="board.uid">
{{ board.title }} ({{ formatCurrency(board.price) }} pro Person und Nacht)
</option>
</select>
<div class="text-sm text-red-600 mt-1"
v-show="formErrors.board"
v-html="formErrors.board"></div>
<div v-show="selectedPax < 30">
<span class="text-red-600">Erst ab 30 Personen buchbar.</span>
</div>
@@ -128,8 +170,8 @@
<td>{{ formatCurrency(priceBoard) }}</td>
</tr>
<tr class="odd:bg-zinc-100"
v-if="pricePax.undersubscription.EUR > 0 || pricePax.undersubscription.CHF > 0">
<th class="text-left py-2">Kleingruppen-Verpflegungszuschlag</th>
v-if="(priceBoard.EUR > 0 || priceBoard.CHF > 0) && (pricePax.undersubscription.EUR > 0 || pricePax.undersubscription.CHF > 0)">
<th class="text-left py-2">Verpflegungs-Aufschlag für Gruppen unter 50 Personen</th>
<td>{{ formatCurrency(pricePax.undersubscription) }}</td>
</tr>
<tr class="odd:bg-zinc-100"
@@ -142,7 +184,7 @@
<td>
<strong>{{ formatCurrency(priceTotal) }}</strong>
<br>
<small>zzgl. vor Ort zu entrichtender Ortstaxe</small>
<small>{{ taxLabel }}</small>
</td>
</tr>
</tbody>
@@ -180,17 +222,38 @@
v-show="formErrors.name"
v-html="formErrors.name"></div>
</div>
<div :class="{ 'has-error': formErrors.address }">
<div :class="{ 'has-error': formErrors.street }">
<label class="font-bold mb-2">
Adresse*
Strasse, Nr.*
</label>
<textarea class="form-field"
cols="30"
rows="3"
v-model="address"/>
<input type="text"
class="form-field"
v-model="street">
<div class="text-sm text-red-600 mt-1"
v-show="formErrors.address"
v-html="formErrors.address"></div>
v-show="formErrors.street"
v-html="formErrors.street"></div>
</div>
<div :class="{ 'has-error': formErrors.postcode }">
<label class="font-bold mb-2">
Postleitzahl*
</label>
<input type="text"
class="form-field"
v-model="postcode">
<div class="text-sm text-red-600 mt-1"
v-show="formErrors.postcode"
v-html="formErrors.postcode"></div>
</div>
<div :class="{ 'has-error': formErrors.city }">
<label class="font-bold mb-2">
Ort*
</label>
<input type="text"
class="form-field"
v-model="city">
<div class="text-sm text-red-600 mt-1"
v-show="formErrors.city"
v-html="formErrors.city"></div>
</div>
<div :class="{ 'has-error': formErrors.group }">
<label class="font-bold mb-2">
@@ -239,7 +302,7 @@
<input type="checkbox" v-model="confirmation" name="confirmation" id="confirmation">
<span class="block ml-2">
Durch Anklicken des Buttons 'Buchung abschicken' bestätige ich, dass ich die
<a href="https://www.ep-reisen.de/reisen-fuer-gruppen/infos-zusatzleistungen/reiseinfos/agbs/"
<a :href="termsUrls[countryCode]"
class="text-ep-primary" target="_blank">
Allgemeinen Geschäftsbedingungen (AGB)
</a> gelesen habe und damit einverstanden bin, dass eine
@@ -367,6 +430,14 @@ export default {
type: Number,
default: 5,
},
taxLabel: {
type: String,
default: 'zzgl. vor Ort zu entrichtender Ortstaxe',
},
adolescentsAge: {
type: Number,
default: 0,
}
},
data() {
return {
@@ -382,16 +453,25 @@ export default {
selectedRange: [],
selectedPax: 30,
childrenCount: 0,
childrenCount3to5: 0,
adolescentsCount: 0,
selectedOptions: [],
selectedBoard: null,
name: null,
email: null,
phone: null,
remarks: null,
group: null,
address: null,
name: '',
email: '',
phone: '',
remarks: '',
group: '',
street: '',
postcode: '',
city: '',
mode: 'booking',
confirmation: false,
termsUrls: {
AT: 'https://www.ep-reisen.de/fileadmin/user_upload/allgemein/AGB_Gruppen/AGB_Gruppen_CLLT_Touristik_GmbH.pdf',
CH: 'https://www.ep-reisen.de/fileadmin/user_upload/allgemein/AGB_Gruppen/AVB_Gruppen_AlpineVacation_GmbH.pdf',
IT: 'https://www.ep-reisen.de/fileadmin/user_upload/allgemein/AGB_Gruppen/AGB_Gruppen_E_P_Reisen.pdf',
}
}
},
methods: {
@@ -487,14 +567,19 @@ export default {
'tx_epproducts_ajax[groupsPriceInquiry][confirmation]': confirmation ? '1' : '0',
'tx_epproducts_ajax[groupsPriceInquiry][name]': this.name ? this.name : '',
'tx_epproducts_ajax[groupsPriceInquiry][group]': this.group ? this.group : '',
'tx_epproducts_ajax[groupsPriceInquiry][address]': this.address ? this.address : '',
'tx_epproducts_ajax[groupsPriceInquiry][street]': this.street ? this.street : '',
'tx_epproducts_ajax[groupsPriceInquiry][postcode]': this.postcode ? this.postcode : '',
'tx_epproducts_ajax[groupsPriceInquiry][city]': this.city ? this.city : '',
'tx_epproducts_ajax[groupsPriceInquiry][email]': this.email? this.email : '',
'tx_epproducts_ajax[groupsPriceInquiry][phone]': this.phone ? this.phone : '',
'tx_epproducts_ajax[groupsPriceInquiry][remarks]': this.remarks,
'tx_epproducts_ajax[groupsPriceInquiry][dateFrom]': this.selectedFrom.format('DD.MM.YYYY'),
'tx_epproducts_ajax[groupsPriceInquiry][dateTo]': this.selectedTo.format('DD.MM.YYYY'),
'tx_epproducts_ajax[groupsPriceInquiry][nights]': this.nightsCount,
'tx_epproducts_ajax[groupsPriceInquiry][pax]': this.selectedPax,
'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][paxAdditional]': this.formatCurrency(this.pricePax.additional),
'tx_epproducts_ajax[groupsPriceInquiry][summary][undersubscription]': this.formatCurrency(this.pricePax.undersubscription),
@@ -531,6 +616,17 @@ export default {
},
},
computed: {
isSelfCatering() {
let selectedBoard = this.selectedBoard
if (null === selectedBoard) {
return false
}
if ('CH' === this.countryCode) {
return selectedBoard.price.CHF === 0
} else {
return selectedBoard.price.EUR === 0
}
},
paxCount() {
// subtract number of children from pax for price calculation, but minimum 30 pax
return Math.max(this.selectedPax - this.childrenCount, 30)
@@ -567,18 +663,18 @@ export default {
base.EUR += config.price.EUR
base.CHF += config.price.CHF
let included = config.personsIncluded
let additionalPax = this.paxCount - included
if (this.selectedPax > included) {
if (this.paxCount > included) {
let additionalPax = this.paxCount - included
additional.EUR += additionalPax * config.priceAdditionalPerson.EUR
additional.CHF += additionalPax * config.priceAdditionalPerson.CHF
}
}
}
}
if (this.selectedBoard && this.paxCount < 40) {
if (false === this.isSelfCatering && this.paxCount < 40) {
undersubscription.EUR = this.paxCount * this.undersubscription30Eur * this.selectedRange.length
undersubscription.CHF = this.paxCount * this.undersubscription30Chf * this.selectedRange.length
} else if (this.selectedBoard && this.paxCount < 50) {
} else if (false === this.isSelfCatering && this.paxCount < 50) {
undersubscription.EUR = this.paxCount * this.undersubscription40Eur * this.selectedRange.length
undersubscription.CHF = this.paxCount * this.undersubscription40Chf * this.selectedRange.length
}
@@ -87,12 +87,21 @@
<trans-unit id="tx_eptheme.message.group.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.address.1221560718">
<trans-unit id="tx_eptheme.message.street.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.postcode.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.city.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.phone.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.board.1221560910">
<source>Bitte auswählen</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.contactForm.firstName.1221560718">
<source>Bitte angeben</source>
</trans-unit>
@@ -264,6 +264,10 @@
font-weight:normal;
text-decoration:underline;
}
table.data th,
table.data td {
vertical-align: top;
}
@media only screen and (min-width:768px){
.templateContainer {
width:600px !important;
@@ -7,7 +7,7 @@
<f:link.typolink parameter="{f:if(condition: settings.logoLink, then: settings.logoLink, else: settings.defaultHomeUid)}"
class="block lg:pb-4"
title="zur Startseite">
<f:image src="Logo"
<f:image src="{settings.logoImage}"
class="block w-auto {ep:themeClasses(classes: '{ep: \'h-10 lg:h-12\', sbw: \'h-14 lg:h-24\', snz: \'h-14 lg:h-24\', suz: \'h-14 lg:h-24\', uch: \'h-12 lg:h-20\', ser: \'h-10 lg:h-12\'}', key: settings.themekey)}"
alt="Logo"/>
</f:link.typolink>
@@ -24,7 +24,7 @@
data-lightbox-url-value="{f:uri.action(action: 'index', controller: 'GroupsPrice', arguments: '{hotel: hotel}', pageUid: settings.groupsPricePopupPageUid, absolute: 1)}"
data-lightbox-type-value="external"
data-action="lightbox#open">
Preisberechnung &amp; direkt buchen
Preisrechner/Buchungstool
</button>
</div>
</f:if>
@@ -4,23 +4,27 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<div class="odd:bg-zinc-50 p-4" data-rte-content>
<h3>
Eigenanreise
</h3>
{journey.byCar -> f:format.html()}
<f:if condition="{journey.image}">
<ep:lazyLoadImage image="{journey.image}"
loaderimg="EXT:ep_theme/Resources/Public/images/lazy.png"
class="block w-full h-auto"
width="770"
height="430c+100"
alt="{journey.image.alternative}" />
</f:if>
<f:if condition="{journey.byBus}">
<h3>
Busanreise
</h3>
{journey.byBus -> f:format.html()}
<div class="pb-8">
{journey.byBus -> f:format.html()}
</div>
</f:if>
<f:if condition="{journey.byCar}">
<h3>
Eigenanreise
</h3>
{journey.byCar -> f:format.html()}
<f:if condition="{journey.image}">
<ep:lazyLoadImage image="{journey.image}"
loaderimg="EXT:ep_theme/Resources/Public/images/lazy.png"
class="block w-full h-auto"
width="770"
height="430c+100"
alt="{journey.image.alternative}" />
</f:if>
</f:if>
</div>
@@ -12,7 +12,7 @@
data-lightbox-url-value="{f:uri.action(action: 'index', controller: 'GroupsPrice', arguments: '{hotel: hotel}', pageUid: settings.groupsPricePopupPageUid, absolute: 1)}"
data-lightbox-type-value="external"
data-action="lightbox#open">
Preisberechnung &amp; direkt buchen
Preisrechner/Buchungstool
</button>
</div>
</f:if>
@@ -20,12 +20,12 @@
<f:section name="Table">
<div class="w-full max-w-full overflow-x-scroll md:overflow-x-auto">
<table class="min-w-full border border-zinc-200 border-collapse mb-8">
<table class="min-w-full border border-zinc-200 border-collapse mb-2">
<f:for each="{categories}" as="category" key="categoryKey">
<f:if condition="{v:variable.get(name: 'pricetable.{categoryKey}', useRawKeys: 1) -> f:count()} > 0">
<tr class="text-white {ep:themeClasses(classes: '{sbw: \'bg-sbw-primary\', snz: \'bg-snz-primary\', uch: \'bg-uch-primary\'}', key: themekey)}">
<td class="px-2 py-1 font-bold border border-zinc-200">
Apartments {category}
{category}
</td>
<f:for each="{roomTypesLabels}" as="type">
<td class="px-2 py-1 border border-zinc-200">
@@ -36,33 +36,34 @@
<f:for each="{pricetable.{categoryKey}}" as="row">
<tr>
<td class="px-2 py-1 border border-zinc-200">
<f:if condition="{row.hotelShortName}">
<f:then>
{row.hotelShortName}
</f:then>
<f:else>
<f:if condition="{row.hotelTitle}">
<f:then>
{row.hotelTitle}
</f:then>
<f:else>
{row.hotelName}
</f:else>
</f:if>
</f:else>
</f:if>
<f:link.typolink parameter="{row.hotelUri}" class="underline">
<f:if condition="{row.hotelShortName}">
<f:then>
{row.hotelShortName}
</f:then>
<f:else>
<f:if condition="{row.hotelTitle}">
<f:then>
{row.hotelTitle}
</f:then>
<f:else>
{row.hotelName}
</f:else>
</f:if>
</f:else>
</f:if>
</f:link.typolink>
</td>
<f:for each="{roomTypesPax}" as="type">
<td class="px-2 py-1 border border-zinc-200">
<f:if condition="{row.rooms.{type}.available}">
<f:then>
<a class="{ep:themeClasses(classes: '{sbw: \'text-sbw-primary\', snz: \'text-snz-primary\', uch: \'text-uch-primary\'}', key: settings.themekey)}"
data-controller="datalayer" data-datalayer-event-value="ZurBuchung" data-action="datalayer#trigger"
href="{row.bookingUrl}"
title="Jetzt buchen"
target="_blank">
<f:link.typolink
parameter="{row.hotelUri}"
class="whitespace-nowrap {ep:themeClasses(classes: '{sbw: \'text-sbw-primary\', snz: \'text-snz-primary\', uch: \'text-uch-primary\'}', key: settings.themekey)}">
{row.rooms.{type}.price}
</a>
{f:if(condition: '{row.rooms.{type}.undersubscription}', then: '*')}
</f:link.typolink>
</f:then>
<f:else>
<f:if condition="{row.rooms.{type}.available}">
@@ -79,6 +80,7 @@
</f:if>
</f:for>
</table>
<p class="text-sm mb-8">* Preis kommt durch eine Unterbelegung zustande.</p>
</div>
</f:section>
@@ -13,7 +13,7 @@
<h1>Anfrage Gruppenhaus vom <f:format.date date="now" format="d.m.y" /></h1>
</f:else>
</f:if>
<table>
<table class="data">
<tr>
<th>
Art
@@ -35,8 +35,16 @@
<td>{group}</td>
</tr>
<tr>
<th>Adresse</th>
<td>{address -> f:format.nl2br()}</td>
<th>Straße</th>
<td>{street}</td>
</tr>
<tr>
<th>Postleitzahl</th>
<td>{postcode}</td>
</tr>
<tr>
<th>Ort</th>
<td>{city}</td>
</tr>
<tr>
<th>E-Mail</th>
@@ -50,6 +58,10 @@
<th>Zeitraum</th>
<td>{dateFrom} - {dateTo}</td>
</tr>
<tr>
<th>Nächte</th>
<td>{nights}</td>
</tr>
<tr>
<th>Anzahl der Personen</th>
<td>{pax -> v:or(alternative: '-')}</td>
@@ -58,6 +70,16 @@
<th>Anzahl Kinder 0-3 Jahre</th>
<td>{children -> v:or(alternative: '-')}</td>
</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>
<th>Verpflegung</th>
<td>
@@ -82,25 +104,55 @@
</f:for>
</ul>
</f:then>
<f:else>
-
</f:else>
</f:if>
</td>
</tr>
<tr>
<th>Kostenübersicht</th>
<td>
Grundpreis: {summary.paxBase}<br>
Aufpreis Personen: {summary.paxAdditional -> v:or(alternative: '-')}<br>
Verpflegung: {summary.board -> v:or(alternative: '-')}<br>
Kleingruppen-Verpflegungszuschlag: {summary.undersubscription -> v:or(alternative: '-')}<br>
Aufpreis Kurzzeit: {summary.shortTerm -> v:or(alternative: '-')}<br>
Zusatzleistungen: {summary.options}<br>
Strom- und Abfallgebühren: {summary.runningCosts -> v:or(alternative: '-')}<br>
<strong>Gesamtpreis: {summary.total}</strong>
<ul>
<li>
Grundpreis: {summary.paxBase}
</li>
<li>
Aufpreis Personen: {summary.paxAdditional -> v:or(alternative: '-')}
</li>
<li>
Verpflegung: {summary.board -> v:or(alternative: '-')}
</li>
<li>
Kleingruppen-Verpflegungszuschlag: {summary.undersubscription -> v:or(alternative: '-')}
</li>
<li>
Aufpreis Kurzzeit: {summary.shortTerm -> v:or(alternative: '-')}
</li>
<li>
Zusatzleistungen: {summary.options}
</li>
<li>
Strom- und Abfallgebühren: {summary.runningCosts -> v:or(alternative: '-')}
</li>
<li>
<strong>Gesamtpreis: {summary.total}</strong>
</li>
</ul>
</td>
</tr>
<tr>
<th>Bemerkungen/Wünsche</th>
<td>{remarks -> f:format.nl2br()}</td>
<td>
<f:if condition="{remarks}">
<f:then>
{remarks -> f:format.nl2br()}
</f:then>
<f:else>
-
</f:else>
</f:if>
</td>
</tr>
</table>
</f:section>
@@ -26,6 +26,8 @@
logo-at="{f:uri.image(src: 'fileadmin/user_upload/Logos_Banner/Cllt_Touristik_frei_gestellt.jpg')}"
main-season-from="{mainSeasonFrom}"
main-season-to="{mainSeasonTo}"
tax-label="{hotel.groupsPriceTaxLabel -> v:or(alternative: 'zzgl. vor Ort zu entrichtender Ortstaxe')}"
adolescents-age="{adolescentsAge}"
>
</groups-price-calculator>
</f:format.raw>
@@ -339,7 +339,7 @@
data-lightbox-type-value="external"
data-lightbox-height-value="85vh"
data-action="lightbox#open">
Preisberechnung &amp; direkt buchen
Preisrechner/Buchungstool
</button>
</f:if>
<div class="bg-zinc-50 p-4 flex flex-col space-y-2 relative mb-8">