Implement group bookings price calculator

This commit is contained in:
Björn Fromme
2020-01-14 19:00:51 +01:00
parent 932fb40ae6
commit b85ec4d5f3
30 changed files with 877 additions and 238 deletions
+20 -19
View File
@@ -1,4 +1,3 @@
rootPageId: 9
base: / base: /
baseVariants: baseVariants:
- -
@@ -7,6 +6,12 @@ baseVariants:
- -
base: 'https://schnee-event.burn.dpn' base: 'https://schnee-event.burn.dpn'
condition: 'applicationContext == "Development/Skimax"' condition: 'applicationContext == "Development/Skimax"'
disableStaticFileCache: true
errorHandling:
-
errorCode: '404'
errorHandler: Page
errorContentSource: 't3://page?uid=608'
languages: languages:
- -
title: Deutsch title: Deutsch
@@ -34,16 +39,7 @@ languages:
fallbackType: strict fallbackType: strict
fallbacks: '' fallbacks: ''
flag: gb flag: gb
errorHandling: rootPageId: 9
-
errorCode: '404'
errorHandler: Page
errorContentSource: 't3://page?uid=608'
routes:
-
route: robots.txt
type: staticText
content: "User-agent: *\r\nDisallow: /typo3/\r\nDisallow: /typo3_src/\r\nAllow: /typo3/sysext/frontend/Resources/Public/*\r\n"
routeEnhancers: routeEnhancers:
NewsDetailPlugin: NewsDetailPlugin:
type: Extbase type: Extbase
@@ -232,11 +228,16 @@ routeEnhancers:
default: / default: /
index: '' index: ''
map: map:
'/': 0 /: 0
'/json': 1701 /json: 1701
'/html': 1702 /html: 1702
'.json': 1803 .json: 1803
'.ical': 1710 .ical: 1710
'sitemap.xml': 1533906435 sitemap.xml: 1533906435
'yoast-snippetpreview.json': 1480321830 yoast-snippetpreview.json: 1480321830
'/pxa': 7378121 /pxa: 7378121
routes:
-
route: robots.txt
type: staticText
content: "User-agent: *\r\nDisallow: /typo3/\r\nDisallow: /typo3_src/\r\nAllow: /typo3/sysext/frontend/Resources/Public/*\r\n"
@@ -1,5 +1,4 @@
<?php <?php
namespace EP\EpProducts\Controller; namespace EP\EpProducts\Controller;
/*************************************************************** /***************************************************************
@@ -27,69 +26,106 @@ namespace EP\EpProducts\Controller;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\GroupsPriceBoard;
use EP\EpProducts\Domain\Model\Hotel; use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Service\GroupsPriceService; use EP\EpProducts\Domain\Repository\GroupsPriceOptionRepository;
use League\Period\Period; use EP\EpProducts\Service\EmailService;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException; use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
class AjaxGroupsPriceController extends ActionController class AjaxGroupsPriceController extends ActionController
{ {
/** /**
* @var GroupsPriceService * @var GroupsPriceOptionRepository
*/ */
protected $groupsPriceService; protected $groupsPriceOptionRepository;
public function __construct(GroupsPriceService $groupsPriceService) /**
* @var EmailService
*/
protected $emailService;
public function __construct(GroupsPriceOptionRepository $groupsPriceOptionRepository, EmailService $emailService)
{ {
parent::__construct(); parent::__construct();
$this->groupsPriceService = $groupsPriceService; $this->groupsPriceOptionRepository = $groupsPriceOptionRepository;
$this->emailService = $emailService;
} }
public function initializeAction() public function initializeProcessFormAction()
{ {
if ($this->request->hasArgument('dateFrom')) { if ($this->request->hasArgument('options')) {
try { $optionUids = $this->request->getArgument('options');
$dateFrom = new \DateTime($this->request->getArgument('dateFrom')); $options = $this->groupsPriceOptionRepository->findByUids($optionUids)->toArray();
} $this->request->setArgument('options', $options);
catch (\Throwable $e) {
$dateFrom = null;
}
$this->request->setArgument('dateFrom', $dateFrom);
}
if ($this->request->hasArgument('dateTo')) {
try {
$dateTo = new \DateTime($this->request->getArgument('dateTo'));
}
catch (\Throwable $e) {
$dateTo = null;
}
$this->request->setArgument('dateTo', $dateTo);
} }
} }
/** /**
* @param Hotel $hotel * @param Hotel $hotel
* @param \DateTime $dateFrom * @param string $name
* @param \DateTime $dateTo * @param string $email
* @param string $dateFrom
* @param string $dateTo
* @param int $pax * @param int $pax
* @return string * @param array $options
* @throws \League\Period\Exception * @param GroupsPriceBoard $board
*
* @Extbase\Validate("NotEmpty", param="name")
* @Extbase\Validate("NotEmpty", param="email")
* @Extbase\Validate("EmailAddress", param="email")
*/ */
public function indexAction(Hotel $hotel, \DateTime $dateFrom = null, \DateTime $dateTo = null, $pax = 0) public function processFormAction(Hotel $hotel, $name, $email, $dateFrom, $dateTo, $pax = 30, array $options = [], GroupsPriceBoard $board = null)
{ {
if (null !== $dateFrom && null !== $dateTo) { $emailOptions = [
$period = new Period($dateFrom, $dateTo); 'toEmail' => $this->settings['groupsPriceToEmail'],
$price = $this->groupsPriceService->calculateHotelPrice($hotel, $period, $pax); 'toName' => $this->settings['groupsPriceToName'],
'fromEmail' => $this->settings['groupsPriceToEmail'],
'fromName' => $this->settings['groupsPriceToName'],
'subject' => 'Gruppenhaus Preiskalkulator/Anfrage',
'templateName' => 'GroupsPrice',
];
$variables = [
'hotel' => $hotel,
'name' => $name,
'email' => $email,
'dateFrom' => $dateFrom,
'dateTo' => $dateTo,
'pax' => $pax,
'options' => $this->groupsPriceOptionRepository->findByUids($options)->toArray(),
'board' => $board,
];
$this->emailService->send($emailOptions, $variables);
return json_encode(['status' => 'ok'], JSON_THROW_ON_ERROR, 512);
}
/**
* @return string
*/
protected function errorAction() {
$formErrors = [];
if ($this->arguments->validate()->hasErrors()) {
foreach ($this->arguments->validate()->getFlattenedErrors() as $key => $errors)
{
$parts = explode('.', $key);
$fieldName = array_pop($parts);
$errorsRaw = [];
foreach ($errors as $error) {
$translationKey = sprintf('tx_eptheme.message.%s.%s', $fieldName, $error->getCode());
$errorsRaw[] = LocalizationUtility::translate($translationKey, 'ep_theme');
}
$formErrors[$fieldName] = implode(', ', $errorsRaw);
}
} }
return json_encode([ $response['status'] = 'validation';
'price' => $price ?? [], $response['errors'] = $formErrors;
'configs' => $hotel->getGroupsPriceConfigs()->toArray(),
'boards' => $hotel->getGroupsPriceBoards()->toArray(), return json_encode($response);
'options' => $hotel->getGroupsPriceOptions()->toArray(),
], JSON_THROW_ON_ERROR, 512);
} }
} }
@@ -0,0 +1,42 @@
<?php
namespace EP\EpProducts\Controller;
/***************************************************************
*
* Copyright notice
*
* (c) 2020 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use EP\EpProducts\Domain\Model\Hotel;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class GroupsPriceController extends ActionController
{
public function indexAction(Hotel $hotel)
{
$this->view->assign('hotel', $hotel);
$this->view->assign('configs', json_encode($hotel->getGroupsPriceConfigs()->toArray()));
$this->view->assign('boards', json_encode($hotel->getGroupsPriceBoards()->toArray()));
$this->view->assign('options', json_encode($hotel->getGroupsPriceOptions()->toArray()));
}
}
@@ -79,6 +79,7 @@ class GroupsPriceBoard extends AbstractEntity implements \JsonSerializable
public function jsonSerialize() public function jsonSerialize()
{ {
return [ return [
'uid' => $this->getUid(),
'title' => $this->getTitle(), 'title' => $this->getTitle(),
'price' => $this->getPrice(), 'price' => $this->getPrice(),
]; ];
@@ -142,11 +142,12 @@ class GroupsPriceConfig extends AbstractEntity implements \JsonSerializable
public function jsonSerialize() public function jsonSerialize()
{ {
return [ return [
'dateFrom' => $this->getDateFrom()->format('d.m.Y'), 'uid' => $this->getUid(),
'dateTo' => $this->getDateTo()->format('d.m.Y'), 'dateFrom' => $this->getDateFrom()->format('Y-m-d'),
'dateTo' => $this->getDateTo()->format('Y-m-d'),
'personsIncluded' => $this->getPersonsIncluded(), 'personsIncluded' => $this->getPersonsIncluded(),
'price' => $this->getPrice(), 'price' => $this->getPrice(),
'priceAdditionalPerson' => $this->getPriceAdditionalPerson(), 'priceAdditionalPerson' => $this->getPriceAdditionalPerson(),
]; ];
} }
} }
@@ -31,6 +31,11 @@ use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class GroupsPriceOption extends AbstractEntity implements \JsonSerializable class GroupsPriceOption extends AbstractEntity implements \JsonSerializable
{ {
const TYPE_GLOBAL = 1;
const TYPE_PER_PAX = 2;
const TYPE_PER_NIGHT = 3;
const TYPE_PER_PAX_AND_NIGHT = 4;
/** /**
* @var string * @var string
*/ */
@@ -41,6 +46,16 @@ class GroupsPriceOption extends AbstractEntity implements \JsonSerializable
*/ */
protected $price; protected $price;
/**
* @var int
*/
protected $type;
/**
* @var bool
*/
protected $ignoreWithBoard = false;
/** /**
* @return string * @return string
*/ */
@@ -73,14 +88,49 @@ class GroupsPriceOption extends AbstractEntity implements \JsonSerializable
$this->price = $price; $this->price = $price;
} }
/**
* @return int
*/
public function getType(): int
{
return $this->type;
}
/**
* @param int $type
*/
public function setType(int $type): void
{
$this->type = $type;
}
/**
* @return bool
*/
public function isIgnoreWithBoard(): bool
{
return $this->ignoreWithBoard;
}
/**
* @param bool $ignoreWithBoard
*/
public function setIgnoreWithBoard(bool $ignoreWithBoard): void
{
$this->ignoreWithBoard = $ignoreWithBoard;
}
/** /**
* @return array * @return array
*/ */
public function jsonSerialize() public function jsonSerialize()
{ {
return [ return [
'uid' => $this->getUid(),
'title' => $this->getTitle(), 'title' => $this->getTitle(),
'price' => $this->getPrice(), 'price' => $this->getPrice(),
'type' => $this->getType(),
'ignoreWithBoard' => $this->isIgnoreWithBoard(),
]; ];
} }
} }
@@ -0,0 +1,47 @@
<?php
namespace EP\EpProducts\Domain\Repository;
/***************************************************************
*
* Copyright notice
*
* (c) 2020 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
use TYPO3\CMS\Extbase\Persistence\Repository;
class GroupsPriceOptionRepository extends Repository
{
public function findByUids(array $uids)
{
/** @var Typo3QuerySettings $querySettings */
$querySettings = $this->objectManager->get(Typo3QuerySettings::class);
$querySettings->setRespectStoragePage(false);
$this->setDefaultQuerySettings($querySettings);
$query = $this->createQuery();
$query->matching($query->in('uid', $uids));
return $query->execute();
}
}
@@ -0,0 +1,122 @@
<?php
namespace EP\EpProducts\Service;
/***************************************************************
*
* Copyright notice
*
* (c) 2020 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use Symfony\Component\OptionsResolver\OptionsResolver;
use TYPO3\CMS\Core\Mail\MailMessage;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Fluid\View\StandaloneView;
class EmailService implements SingletonInterface
{
/**
* @var ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @var array
*/
protected $settings;
/**
* @param ConfigurationManagerInterface $manager
*/
public function injectConfigurationManager(ConfigurationManagerInterface $manager)
{
$this->configurationManager = $manager;
$settings = GeneralUtility::removeDotsFromTS(
$this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT)
);
$this->settings = $settings['plugin']['tx_eptheme'];
}
/**
* @param array $options
* @param array $variables
* @return bool
*/
public function send(array $options, array $variables)
{
$resolvedOptions = $this->resolveOptions($options);
/** @var MailMessage $message */
$message = GeneralUtility::makeInstance(MailMessage::class);
$message
->setTo([$resolvedOptions['toEmail'] => $resolvedOptions['toName']])
->setFrom([$resolvedOptions['fromEmail'] => $resolvedOptions['fromName']])
->setSubject($resolvedOptions['subject'])
;
$view = $this->getView($resolvedOptions['templateName']);
$view->assignMultiple($variables);
$message->setBody($view->render(), 'text/html');
return $message->send();
}
/**
* @param string $templateName
* @param string $format
* @return StandaloneView
*/
protected function getView($templateName, $format = 'html')
{
/** @var StandaloneView $view */
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->setFormat($format);
$view->setTemplateRootPaths($this->settings['view']['templateRootPaths']);
$view->setLayoutRootPaths($this->settings['view']['layoutRootPaths']);
$view->setPartialRootPaths($this->settings['view']['partialRootPaths']);
$template = GeneralUtility::getFileAbsFileName('EXT:ep_theme/Resources/Private/Templates/Email/'.$templateName.'.'.$format);
$view->setTemplatePathAndFilename($template);
return $view;
}
/**
* @param array $options
* @return array
*/
protected function resolveOptions(array $options)
{
$optionsResolver = new OptionsResolver();
$optionsResolver->setDefined(['toEmail', 'toName', 'subject', 'templateName', 'fromEmail', 'fromName']);
$optionsResolver->setRequired(['toEmail', 'toName', 'subject', 'templateName']);
$optionsResolver->setDefaults([
'fromEmail' => $this->settings['settings']['contactFormToEmail'],
'fromName' => $this->settings['settings']['contactFormToName'],
]);
return $optionsResolver->resolve($options);
}
}
@@ -1,105 +0,0 @@
<?php
namespace EP\EpProducts\Service;
use EP\EpProducts\Domain\Model\Hotel;
use League\Period\Period;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class GroupsPriceService
{
/**
* @param Hotel $hotel
* @param Period $period
* @param int $pax
* @return array
*/
public function calculateHotelPrice(Hotel $hotel, Period $period, $pax)
{
// Get all price configs for provided hotel
$configs = $this->getConfigs($hotel);
// Create periods in configs to simplify following steps
$this->patchConfigPeriods($configs);
$nights = 0;
$price = 0;
// Iterate over provided period
foreach ($period->getDatePeriod('1 DAY') as $day) {
// Iterate over all configs
foreach ($configs as $config) {
// Skip config if current day is not contained
if (false === $config['period']->contains($day)) {
continue;
}
// Calculate base price
$price += $config['price'];
// Add costs for not-included number of persons
if ($pax > $config['persons_included']) {
$price += $config['price_additional_person'] * ($pax - $config['persons_included']);
}
}
$nights++;
}
if ($nights === 2) {
$shortTermCosts = $price * .2;
}
if ($nights === 3) {
$shortTermCosts = $price * .1;
}
$electricityCosts = ($nights - 1) * $pax * 1.7;
return [
'pax' => $pax,
'basePrice' => $price,
'shortTermCosts' => $shortTermCosts ?? 0.0,
'electricityCosts' => $electricityCosts,
'nights' => $nights,
];
}
protected function patchConfigPeriods(array &$configs)
{
foreach ($configs as $idx => $config) {
$configDateFrom = (new \DateTimeImmutable())->setTimestamp($config['date_from']);
$configDateTo = (new \DateTimeImmutable())->setTimestamp($config['date_to']);
$configs[$idx]['period'] = new Period($configDateFrom, $configDateTo);
}
}
/**
* @param Hotel $hotel
* @return array
*/
protected function getConfigs(Hotel $hotel)
{
$qb = $this->getQueryBuilder();
return $qb
->select('*')
->from('tx_epproducts_domain_model_groupspriceconfig')
->where($qb->expr()->eq('hotel', $qb->createNamedParameter($hotel->getUid())))
->orderBy('date_from')
->execute()
->fetchAll()
;
}
protected function getQueryBuilder()
{
/** @var ConnectionPool $connectionPool */
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
return $connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_groupspriceconfig');
}
}
@@ -26,7 +26,7 @@ call_user_func(function () {
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'product_detail', 'product_detail',
'Produktdetails' 'Produkt: Details'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
@@ -38,7 +38,7 @@ call_user_func(function () {
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'product_teasergroup', 'product_teasergroup',
'Produktteaser' 'Produkt: Teaser'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
@@ -56,61 +56,67 @@ call_user_func(function () {
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'region_teasergroup', 'region_teasergroup',
'Gebietsteaser' 'Gebiet: Teaser'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'region_detail', 'region_detail',
'Gebietsdetails' 'Gebiet: Details'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'city_teasergroup', 'city_teasergroup',
'Ortsteaser' 'Ort: Teaser'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'city_detail', 'city_detail',
'Ortsdetails' 'Ort: Details'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'hotel_teasergroup', 'hotel_teasergroup',
'Hotelteaser' 'Hotel: Teaser'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'hotel_teasergroup_nodate', 'hotel_teasergroup_nodate',
'Hotelteaser (terminunabhängig)' 'Hotel: Teaser (terminunabhängig)'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'hotel_detail', 'hotel_detail',
'Hoteldetails' 'Hotel: Details'
);
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts',
'hotel_groups_price',
'Hotel: Preiskalulator Gruppen'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'hotel_detail_event', 'hotel_detail_event',
'Hoteldetails (Event)' 'Hotel: Details (Event)'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'concept_teasergroup', 'concept_teasergroup',
'Konzeptteaser' 'Konzept: Teaser'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'EP.EpProducts', 'EP.EpProducts',
'concept_detail', 'concept_detail',
'Konzeptdetails' 'Konzept: Details'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
@@ -24,14 +24,16 @@ return [
'hideTable' => true, 'hideTable' => true,
], ],
'interface' => [ 'interface' => [
'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, title, price', 'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, title, price, type,
ignore_with_board',
], ],
'types' => [ 'types' => [
'1' => [ '1' => [
'showitem' => 'title, price', 'showitem' => 'title, --palette--;;priceconfig',
], ],
], ],
'palettes' => [ 'palettes' => [
'priceconfig' => ['showitem' => 'price, type, ignore_with_board'],
], ],
'columns' => [ 'columns' => [
@@ -140,6 +142,29 @@ return [
'eval' => 'double2,required', 'eval' => 'double2,required',
] ]
], ],
'type' => [
'exclude' => false,
'label' => 'Typ',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [
['pauschal', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_GLOBAL],
['pro Person', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_PER_PAX],
['pro Nacht', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_PER_NIGHT],
['pro Person und Nacht', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_PER_PAX_AND_NIGHT],
],
'default' => \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_GLOBAL,
]
],
'ignore_with_board' => [
'exclude' => false,
'label' => 'Entfällt bei Verpflegung',
'config' => [
'type' => 'check',
'default' => 0,
]
],
'sorting' => [ 'sorting' => [
'config' => [ 'config' => [
'type' => 'passthrough' 'type' => 'passthrough'
@@ -38,6 +38,10 @@ plugin.tx_epproducts {
searchLogIgnoreIps = searchLogIgnoreIps =
# cat=plugin.tx_epproducts//a; type=string; label=Page UID with reseller export plugin # cat=plugin.tx_epproducts//a; type=string; label=Page UID with reseller export plugin
resellerExportPageUid = resellerExportPageUid =
# cat=plugin.tx_epproducts//a; type=string; label=Email recipient for groups price inquiries
groupsPriceToEmail = [email protected]
# cat=plugin.tx_epproducts//a; type=string; label=Email recipient for groups price inquiries
groupsPriceToName = EP Reisen
} }
persistence { persistence {
# cat=plugin.tx_epproducts//a; type=string; label=Default storage PID # cat=plugin.tx_epproducts//a; type=string; label=Default storage PID
@@ -42,6 +42,9 @@ plugin.tx_epproducts {
ratingPageUid = {$plugin.tx_eptheme.settings.ratingPageUid} ratingPageUid = {$plugin.tx_eptheme.settings.ratingPageUid}
searchPageHeaderImage = {$plugin.tx_eptheme.settings.searchPageHeaderImage} searchPageHeaderImage = {$plugin.tx_eptheme.settings.searchPageHeaderImage}
bpnBookingUrlTemplateCode = {$plugin.tx_eptheme.settings.bpnBookingUrlTemplateCode} bpnBookingUrlTemplateCode = {$plugin.tx_eptheme.settings.bpnBookingUrlTemplateCode}
groupsPricePopupPageUid = {$plugin.tx_eptheme.settings.groupsPricePopupPageUid}
groupsPriceToEmail = {$plugin.tx_epproducts.settings.groupsPriceToEmail}
groupsPriceToName = {$plugin.tx_epproducts.settings.groupsPriceToName}
datePickerPresets { datePickerPresets {
1 { 1 {
label = Silvester label = Silvester
@@ -134,7 +137,7 @@ tx_epproducts_ajax_json {
1 = list 1 = list
} }
AjaxGroupsPrice { AjaxGroupsPrice {
1 = index 1 = processForm
} }
} }
features.requireCHashArgumentForActionArguments = 0 features.requireCHashArgumentForActionArguments = 0
@@ -201,6 +201,17 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\EP\EpProducts\T
] ]
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
'EP.' . $_EXTKEY,
'hotel_groups_price',
[
'GroupsPrice' => 'index',
],
[
'GroupsPrice' => 'index',
]
);
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
'EP.' . $_EXTKEY, 'EP.' . $_EXTKEY,
'hotel_detail_event', 'hotel_detail_event',
@@ -345,7 +356,7 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\EP\EpProducts\T
'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable', 'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable',
'AjaxCalendar' => 'range,contingents,availableRooms', 'AjaxCalendar' => 'range,contingents,availableRooms',
'AjaxWatchlist' => 'list', 'AjaxWatchlist' => 'list',
'AjaxGroupsPrice' => 'index', 'AjaxGroupsPrice' => 'processForm',
], ],
[ [
'AjaxSearch' => 'searchresult', 'AjaxSearch' => 'searchresult',
@@ -356,7 +367,7 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\EP\EpProducts\T
'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable', 'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable',
'AjaxCalendar' => 'range,contingents,availableRooms', 'AjaxCalendar' => 'range,contingents,availableRooms',
'AjaxWatchlist' => 'list', 'AjaxWatchlist' => 'list',
'AjaxGroupsPrice' => 'index', 'AjaxGroupsPrice' => 'processForm',
] ]
); );
+27 -25
View File
@@ -322,35 +322,37 @@ CREATE TABLE tx_epproducts_domain_model_groupspriceconfig
CREATE TABLE tx_epproducts_domain_model_groupspriceoption CREATE TABLE tx_epproducts_domain_model_groupspriceoption
( (
uid int(11) NOT NULL auto_increment, uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL, pid int(11) DEFAULT '0' NOT NULL,
title varchar(255) DEFAULT '' NOT NULL, title varchar(255) DEFAULT '' NOT NULL,
price float(8) unsigned DEFAULT '0' NOT NULL, price float(8) unsigned DEFAULT '0' NOT NULL,
hotel int(11) unsigned DEFAULT '0', type tinyint(4) unsigned DEFAULT '0' NOT NULL,
sorting int(11) DEFAULT '0' NOT NULL, ignore_with_board tinyint(4) unsigned DEFAULT '0' NOT NULL,
hotel int(11) unsigned DEFAULT '0',
sorting int(11) DEFAULT '0' NOT NULL,
tstamp int(11) unsigned DEFAULT '0' NOT NULL, tstamp int(11) unsigned DEFAULT '0' NOT NULL,
crdate int(11) unsigned DEFAULT '0' NOT NULL, crdate int(11) unsigned DEFAULT '0' NOT NULL,
cruser_id int(11) unsigned DEFAULT '0' NOT NULL, cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, deleted tinyint(4) unsigned DEFAULT '0' NOT NULL,
hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, hidden tinyint(4) unsigned DEFAULT '0' NOT NULL,
starttime int(11) unsigned DEFAULT '0' NOT NULL, starttime int(11) unsigned DEFAULT '0' NOT NULL,
endtime int(11) unsigned DEFAULT '0' NOT NULL, endtime int(11) unsigned DEFAULT '0' NOT NULL,
t3ver_oid int(11) DEFAULT '0' NOT NULL, t3ver_oid int(11) DEFAULT '0' NOT NULL,
t3ver_id int(11) DEFAULT '0' NOT NULL, t3ver_id int(11) DEFAULT '0' NOT NULL,
t3ver_wsid int(11) DEFAULT '0' NOT NULL, t3ver_wsid int(11) DEFAULT '0' NOT NULL,
t3ver_label varchar(255) DEFAULT '' NOT NULL, t3ver_label varchar(255) DEFAULT '' NOT NULL,
t3ver_state tinyint(4) DEFAULT '0' NOT NULL, t3ver_state tinyint(4) DEFAULT '0' NOT NULL,
t3ver_stage int(11) DEFAULT '0' NOT NULL, t3ver_stage int(11) DEFAULT '0' NOT NULL,
t3ver_count int(11) DEFAULT '0' NOT NULL, t3ver_count int(11) DEFAULT '0' NOT NULL,
t3ver_tstamp int(11) DEFAULT '0' NOT NULL, t3ver_tstamp int(11) DEFAULT '0' NOT NULL,
t3ver_move_id int(11) DEFAULT '0' NOT NULL, t3ver_move_id int(11) DEFAULT '0' NOT NULL,
sys_language_uid int(11) DEFAULT '0' NOT NULL, sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL, l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob, l10n_diffsource mediumblob,
PRIMARY KEY (uid), PRIMARY KEY (uid),
KEY parent (pid), KEY parent (pid),
@@ -21,6 +21,8 @@ mod {
} }
frontpageEvent < .frontpage frontpageEvent < .frontpage
frontpageEvent.title = Startseite Events frontpageEvent.title = Startseite Events
popup < .frontpage
popup.title = Popup
detail { detail {
title = Detailseite title = Detailseite
config { config {
@@ -58,6 +58,8 @@ plugin.tx_eptheme {
myEpPageUid = myEpPageUid =
# cat=eptheme/200/310; type=string; label=Booking links PID # cat=eptheme/200/310; type=string; label=Booking links PID
bookingLinksPid = bookingLinksPid =
# cat=eptheme/200/330; type=string; label=Groups price popup page UID
groupsPricePopupPageUid =
# cat=eptheme/400/100; type=string; label=Phone number general # cat=eptheme/400/100; type=string; label=Phone number general
phoneNumber = 0221 - 272 276 0 phoneNumber = 0221 - 272 276 0
@@ -66,6 +66,7 @@ plugin.tx_eptheme {
eventDisturberButtonLink = {$plugin.tx_eptheme.settings.eventDisturberButtonLink} eventDisturberButtonLink = {$plugin.tx_eptheme.settings.eventDisturberButtonLink}
googleTagManagerId = {$plugin.tx_eptheme.settings.googleTagManagerId} googleTagManagerId = {$plugin.tx_eptheme.settings.googleTagManagerId}
bpnBookingUrlTemplateCode = {$plugin.tx_eptheme.settings.bpnBookingUrlTemplateCode} bpnBookingUrlTemplateCode = {$plugin.tx_eptheme.settings.bpnBookingUrlTemplateCode}
groupsPricePopupPageUid = {$plugin.tx_eptheme.settings.groupsPricePopupPageUid}
googleStaticMapsBaseUrl = {$plugin.tx_eptheme.settings.googleStaticMapsBaseUrl} googleStaticMapsBaseUrl = {$plugin.tx_eptheme.settings.googleStaticMapsBaseUrl}
googleMapsJsApiUrl = {$plugin.tx_eptheme.settings.googleMapsJsApiUrl} googleMapsJsApiUrl = {$plugin.tx_eptheme.settings.googleMapsJsApiUrl}
googleMapsApiKey = {$plugin.tx_eptheme.settings.googleMapsApiKey} googleMapsApiKey = {$plugin.tx_eptheme.settings.googleMapsApiKey}
@@ -40,6 +40,9 @@ const sliderPrevArrow = '<button class="slider-pagination__arrow slider-paginati
const sliderNextArrow = '<button class="slider-pagination__arrow slider-pagination__arrow--next"><i class="fa fa-2x fa-chevron-right"></i></button>'; const sliderNextArrow = '<button class="slider-pagination__arrow slider-pagination__arrow--next"><i class="fa fa-2x fa-chevron-right"></i></button>';
const sliderDot = '<button class="slider-pagination__dot" data-role="none" role="button" tabindex="0" />'; const sliderDot = '<button class="slider-pagination__dot" data-role="none" role="button" tabindex="0" />';
// Configure fancybox
$.fancybox.defaults.iframe.css.width = '800px';
// Define date filter for vue templates // Define date filter for vue templates
Vue.filter('date', (value) => { Vue.filter('date', (value) => {
return dayjs(value).format('DD.MM.YYYY'); return dayjs(value).format('DD.MM.YYYY');
@@ -67,6 +70,7 @@ new Vue({
HotelList, HotelList,
BackLink, BackLink,
MyEp: () => import('./myep/App.vue'), MyEp: () => import('./myep/App.vue'),
GroupsPriceCalculator: () => import('./components/GroupsPriceCalculator.vue'),
FacebookPixel FacebookPixel
}, },
data () { data () {
@@ -1,53 +1,312 @@
<template> <template>
<div> <div class="form-wrapper form--border">
Foo! <div class="form-group" v-show="!submitted">
<label>Zeitraum</label>
<input type="text" class="form-control datepicker--visible" placeholder="von... bis" ref="picker"/>
</div>
<div class="form-group" v-show="!submitted">
<label>Personenzahl</label>
<input type="number" class="form-control" v-model="selectedPax" @change="onPaxUpdated()"/>
</div>
<div class="form-group" v-show="options.length > 0 && !submitted">
<label>Optionale Zusatzleistungen</label>
<div class="checkbox" v-for="option of options" :key="option.uid">
<label>
<input type="checkbox" v-model="selectedOptions" :value="option">
<span class="cr"><i class="cr-icon glyphicon glyphicon-ok"></i></span>
{{ option.title }}
</label>
</div>
</div>
<div class="form-group" v-show="boards.length > 0 && !submitted">
<label>Optionale Verpflegungsleistungen</label>
<select class="form-control" v-model="selectedBoard" v-if="selectedPax >= 30">
<option :value="null">Keine Auswahl</option>
<option v-for="board of boards" :value="board" :key="board.uid">{{ board.title }}</option>
</select>
<div v-show="selectedPax < 30">
<span class="label label-warning">Erst ab 30 Personen buchbar.</span>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Preise</div>
<div class="panel-body">
<div v-show="nightsCount > 0">
Zeitraum: {{ rangeFormatted }}, {{ nightsCount }} Nächte<br>
Basispreis: {{ pricePax.base | money }} <br>
<span v-if="pricePax.additional > 0">Aufpreis Personenzahl: {{ pricePax.additional | money }} <br></span>
<span v-if="priceShortTerm > 0">Aufpreis Kurzzeit: {{ priceShortTerm | money }} <br></span>
<span v-if="selectedOptions.length > 0">Zusatzleistungen: {{ priceOptions | money }} <br></span>
<span v-if="priceBoard > 0">Verpflegung: {{ priceBoard | money }} <br></span>
<span v-if="priceRunningCosts > 0">Strom- und Abfallgebühren: {{ priceRunningCosts | money }} <br></span>
<span><strong>Gesamtpreis: {{ totalPrice | money }} </strong></span>
</div>
<div v-show="nightsCount === 0">
<span class="label label-warning">Bitte wählen Sie einen Zeitraum</span>
</div>
</div>
</div>
<div class="panel panel-default" v-show="nightsCount > 0 && !submitted">
<div class="panel-heading">Angebot anfordern</div>
<div class="panel-body">
<div class="form-group" :class="{ 'has-error': formErrors.name }">
<label>Name*</label>
<input type="text" class="form-control" v-model="name">
<div class="help-block"
v-show="formErrors.name" v-html="formErrors.name"></div>
</div>
<div class="form-group" :class="{ 'has-error': formErrors.email }">
<label>E-Mail*</label>
<input type="text" class="form-control" v-model="email">
<div class="help-block"
v-show="formErrors.email" v-html="formErrors.email"></div>
</div>
<div class="form-group">
<button class="button" type="submit" @click.prevent="submitForm()">Abschicken</button>
</div>
</div>
</div>
<div class="panel panel-default" v-show="submitted">
<div class="panel-body">
Vielen Dank für Ihre Anfrage. Wir werden Sie schnellstmöglich bearbeiten.
</div>
</div>
<div class="form-overlay" v-show="processing">
<img :src="loaderUri" alt="Loading...">
</div>
</div> </div>
</template> </template>
<script> <script>
import axios from 'axios';
import $ from 'jquery'; import $ from 'jquery';
import dayjs from 'dayjs'
import 'dayjs/locale/de'
import 'flatpickr';
import { German } from 'flatpickr/dist/l10n/de';
import Vue from 'vue';
import axios from 'axios';
export default { export default {
components: {
},
props: { props: {
loaderUri: {
type: String,
required: true
},
endpoint: { endpoint: {
type: String, type: String,
required: true required: true
},
configs: {
type: Array,
required: true
},
boards: {
type: Array,
required: true
},
options: {
type: Array,
required: true
} }
}, },
data () { data () {
return { return {
processing: false, processing: false,
price: {}, submitted: false,
configs: [], picker: null,
boards: [], formErrors: {},
options: [] selectedFrom: null,
selectedTo: null,
selectedRange: [],
selectedPax: 30,
selectedOptions: [],
selectedBoard: null,
name: null,
email: null
}
},
filters: {
money (value) {
if (0 === value) {
return '-';
}
return parseFloat(value).toFixed(2).replace(/\./, ',');
} }
}, },
methods: { methods: {
load () { initSelectableRanges () {
let enabledDates = [];
for (let config of this.configs) {
const dateFrom = dayjs(config.dateFrom);
const dateTo = dayjs(config.dateTo);
enabledDates.push({
from: dateFrom.format('DD.MM.YYYY'),
to: dateTo.format('DD.MM.YYYY')
});
}
this.picker.set('enable', enabledDates);
if (enabledDates.length > 0) {
const firstDate = enabledDates[0].from;
this.picker.set('minDate', firstDate);
}
},
updateSelectedRange () {
let range = [];
let currentDate = this.selectedFrom;
while (currentDate < this.selectedTo) {
range.push(currentDate);
currentDate = currentDate.add(1, 'day');
}
this.selectedRange = range;
},
onPaxUpdated () {
if (this.selectedPax < 30) {
this.selectedBoard = null;
}
},
submitForm () {
let options = [];
for (let option of this.selectedOptions) {
options.push(option.uid);
}
let formData = {
'tx_epproducts_ajax[name]': this.name,
'tx_epproducts_ajax[email]': this.email,
'tx_epproducts_ajax[dateFrom]': this.selectedFrom.format('DD.MM.YYYY'),
'tx_epproducts_ajax[dateTo]': this.selectedTo.format('DD.MM.YYYY'),
'tx_epproducts_ajax[pax]': this.selectedPax,
'tx_epproducts_ajax[board]': this.selectedBoard ? this.selectedBoard.uid : null,
'tx_epproducts_ajax[options]': options
};
this.processing = true; this.processing = true;
this.submitted = false;
axios({ axios({
url: this.endpoint, url: this.endpoint,
method: 'POST', method: 'POST',
data: $.param({ data: $.param(formData)
'tx_epproducts_ajax[pax]': 60,
'tx_epproducts_ajax[dateFrom]': '2020-04-28',
'tx_epproducts_ajax[dateTo]': '2020-04-31'
})
}).then(response => { }).then(response => {
this.configs = response.data.configs; if ('validation' === response.data.status) {
this.boards = response.data.boards; this.formErrors = response.data.errors;
this.options = response.data.options; } else {
this.price = response.data.price; this.submitted = true;
}
}).catch(error => { }).catch(error => {
}).finally(() => { }).finally(() => {
this.processing = false; this.processing = false;
}); });
} }
}, },
computed: {
nightsCount () {
return this.selectedRange.length;
},
rangeFormatted () {
if (null === this.selectedFrom || null === this.selectedTo) {
return '-';
}
return this.selectedFrom.format('DD.MM.YYYY') + ' - ' + this.selectedTo.format('DD.MM.YYYY');
},
pricePax () {
let base = 0;
let additional = 0;
for (let date of this.selectedRange) {
for (let config of this.configs) {
if (date >= dayjs(config.dateFrom) && date < dayjs(config.dateTo)) {
base += config.price;
if (this.selectedPax > config.personsIncluded) {
additional += (this.selectedPax - config.personsIncluded) * config.priceAdditionalPerson;
}
}
}
}
return { base, additional };
},
priceOptions () {
let price = 0;
for (let option of this.selectedOptions) {
if (true === option.ignoreWithBoard && this.selectedBoard) {
continue;
}
if (1 === option.type) {
price += option.price;
} else if (2 === option.type) {
price += option.price * this.selectedPax;
} else if (3 === option.type) {
price += option.price * this.nightsCount;
} else if (4 === option.type) {
price += option.price * this.nightsCount * this.selectedPax;
}
}
return price;
},
priceBoard () {
let price = 0;
if (this.selectedBoard) {
price = this.selectedBoard.price * this.nightsCount * this.selectedPax;
}
return price;
},
priceShortTerm () {
let price = 0;
const totalPricePax = this.pricePax.base + this.pricePax.additional;
if (2 === this.nightsCount) {
price = totalPricePax * 0.2;
}
if (3 === this.nightsCount) {
price = totalPricePax * 0.1;
}
return price;
},
priceRunningCosts () {
if (this.selectedBoard) {
return 0;
}
return this.nightsCount * this.selectedPax * 1.7;
},
totalPrice () {
return this.pricePax.base + this.pricePax.additional + this.priceOptions
+ this.priceBoard + this.priceShortTerm + this.priceRunningCosts;
}
},
mounted () { mounted () {
this.load(); Vue.nextTick(() => {
this.picker = $(this.$refs.picker).flatpickr({
mode: 'range',
dateFormat: 'd.m.Y',
locale: German,
onChange: selectedDates => {
if (2 === selectedDates.length) {
this.selectedFrom = dayjs(selectedDates[0]);
this.selectedTo = dayjs(selectedDates[1]);
this.updateSelectedRange();
}
}
});
this.initSelectableRanges();
});
} }
} }
</script> </script>
<style scoped lang="scss">
.form-wrapper {
position: relative;
}
.form-control[readonly] {
background-color: white;
}
.form-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(white, 0.85);
}
</style>
@@ -28,7 +28,6 @@
DaterangeSelect, DaterangeSelect,
AjaxContent, AjaxContent,
Calendar, Calendar,
GroupsPriceCalculator: () => import('./GroupsPriceCalculator.vue'),
FacebookPixel, FacebookPixel,
WatchlistToggle, WatchlistToggle,
OsmMap, OsmMap,
@@ -42,6 +42,7 @@ $output-bourbon-deprecation-warnings: false;
@import "remix/faq"; @import "remix/faq";
@import "remix/destination-pulldown"; @import "remix/destination-pulldown";
@import "remix/watchlist"; @import "remix/watchlist";
@import "remix/calculator";
// core config // core config
@import "fonts"; @import "fonts";
@@ -0,0 +1,19 @@
.calculator {
position: relative;
}
.calculator__loading {
position: absolute;
@include size(100%);
top: 0;
left: 0;
z-index: 10;
background-color: rgba(white, 0.5);
img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
@@ -47,7 +47,7 @@
} }
.flatpickr-calendar { .flatpickr-calendar {
box-shadow: none; //box-shadow: none;
&.inline { &.inline {
border: 1px solid $color-grey-light; border: 1px solid $color-grey-light;
@@ -75,18 +75,27 @@
<trans-unit id="tx_eptheme.message.contactForm.name.1221560718"> <trans-unit id="tx_eptheme.message.contactForm.name.1221560718">
<source>Bitte angeben</source> <source>Bitte angeben</source>
</trans-unit> </trans-unit>
<trans-unit id="tx_eptheme.message.name.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.contactForm.firstName.1221560718"> <trans-unit id="tx_eptheme.message.contactForm.firstName.1221560718">
<source>Bitte angeben</source> <source>Bitte angeben</source>
</trans-unit> </trans-unit>
<trans-unit id="tx_eptheme.message.contactForm.email.1221560718"> <trans-unit id="tx_eptheme.message.contactForm.email.1221560718">
<source>Bitte angeben</source> <source>Bitte angeben</source>
</trans-unit> </trans-unit>
<trans-unit id="tx_eptheme.message.email.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.contactForm.phone.1221560718"> <trans-unit id="tx_eptheme.message.contactForm.phone.1221560718">
<source>Bitte angeben</source> <source>Bitte angeben</source>
</trans-unit> </trans-unit>
<trans-unit id="tx_eptheme.message.contactForm.email.1221559976"> <trans-unit id="tx_eptheme.message.contactForm.email.1221559976">
<source>Ungültige E-Mail Adresse</source> <source>Ungültige E-Mail Adresse</source>
</trans-unit> </trans-unit>
<trans-unit id="tx_eptheme.message.email.1221559976">
<source>Ungültige E-Mail Adresse</source>
</trans-unit>
<trans-unit id="label.whatsapp_instructions"> <trans-unit id="label.whatsapp_instructions">
<source>Nummer einfach als neuen Kontakt abspeichern und los geht es! Wir beraten Dich über WhatsApp!</source> <source>Nummer einfach als neuen Kontakt abspeichern und los geht es! Wir beraten Dich über WhatsApp!</source>
@@ -0,0 +1,9 @@
<html data-namespace-typo3-fluid="true" lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<div id="app">
<f:render section="Content"/>
</div>
<f:render partial="Config" arguments="{_all}" />
</html>
@@ -0,0 +1,59 @@
<div xmlns="http://www.w3.org/1999/xhtml" lang="en"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:layout name="Email/Default"/>
<f:section name="Main">
<h1>Anfrage Gruppenhaus vom <f:format.date date="now" format="d.m.y" /></h1>
<table>
<tr>
<th>Haus</th>
<td>{hotel.name}</td>
</tr>
<tr>
<th>Name</th>
<td>{name}</td>
</tr>
<tr>
<th>E-Mail</th>
<td>{email}</td>
</tr>
<tr>
<th>Zeitraum</th>
<td>{dateFrom} - {dateTo}</td>
</tr>
<tr>
<th>Anzahl der Personen</th>
<td>{pax}</td>
</tr>
<tr>
<th>Verpflegung</th>
<td>
<f:if condition="{board}">
<f:then>
{board.title}
</f:then>
<f:else>
-
</f:else>
</f:if>
</td>
</tr>
<tr>
<th>Optionale Zusatzleistungen</th>
<td>
<f:if condition="{options}">
<f:then>
<ul>
<f:for each="{options}" as="option">
<li>{option.title}</li>
</f:for>
</ul>
</f:then>
</f:if>
</td>
</tr>
</table>
</f:section>
</div>
@@ -0,0 +1,22 @@
<html data-namespace-typo3-fluid="true" lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ep="http://typo3.org/ns/EP/EpTheme/ViewHelpers"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Preiskalkulator {hotel.name}</h1>
<groups-price-calculator
:configs='{configs -> f:format.raw()}'
:boards='{boards -> f:format.raw()}'
:options='{options -> f:format.raw()}'
endpoint="{ep:uri.ajax(action: 'processForm', controller: 'AjaxGroupsPrice', pageUid: settings.defaultAjaxUid, arguments: '{hotel: hotel}')}"
loader-uri="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif')}"
>
</groups-price-calculator>
</div>
</div>
</div>
</html>
@@ -0,0 +1,11 @@
<html data-namespace-typo3-fluid="true" lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:layout name="Page/Popup"/>
<f:section name="Content">
<f:cObject typoscriptObjectPath="lib.dynamicContent" data="{pageUid: '{data.uid}', colPos: '0'}" />
</f:section>
</html>
@@ -87,13 +87,6 @@
</div> </div>
</div> </div>
<div class="hidden-xs"> <div class="hidden-xs">
<f:comment>
<f:if condition="{product.calendarHotel}">
<groups-price-calculator
endpoint="{ep:uri.ajax(action: 'index', controller: 'AjaxGroupsPrice', extensionName: 'epproducts', pageUid: settings.defaultAjaxUid, arguments: '{hotel: hotel}')}"
></groups-price-calculator>
</f:if>
</f:comment>
<f:render section="Calendar" arguments="{_all}"/> <f:render section="Calendar" arguments="{_all}"/>
<f:render partial="Facts" arguments="{facts: product.facts}"/> <f:render partial="Facts" arguments="{facts: product.facts}"/>
<f:if condition="{product.video}"> <f:if condition="{product.video}">
@@ -255,6 +248,9 @@
<f:section name="Calendar"> <f:section name="Calendar">
<f:if condition="{product.calendarHotel}"> <f:if condition="{product.calendarHotel}">
<a class="button button--full" data-fancybox data-type="iframe" href="{f:uri.action(action: 'index', controller: 'GroupsPrice', arguments: '{hotel: hotel}', pageUid: settings.groupsPricePopupPageUid)}">
Preiskalkulator
</a>
<div class="ep-sidebar ep-facts"> <div class="ep-sidebar ep-facts">
<p><strong>Belegungskalender</strong></p> <p><strong>Belegungskalender</strong></p>
<calendar <calendar