Implement daily bookable dates

This commit is contained in:
Björn Fromme
2018-09-28 12:41:04 +02:00
parent 59694bb410
commit 24adcaebd2
29 changed files with 808 additions and 104 deletions
+2
View File
@@ -9,6 +9,8 @@
"require": {
"php": "^7.1",
"ext-json": "*",
"ext-libxml": "*",
"ext-simplexml": "*",
"typo3/cms": "^8.7",
"typo3/cms-backend": "^8.7",
"typo3/cms-belog": "^8.7",
Generated
+61 -2
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "bf966b3d9286731a2aa6030d0010afed",
"content-hash": "0336ff7055dae975d70aea8153e7b798",
"packages": [
{
"name": "christophlehmann/imageoptimizer",
@@ -1265,6 +1265,63 @@
],
"time": "2018-02-06T08:27:03+00:00"
},
{
"name": "league/period",
"version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/period.git",
"reference": "280b54d737b4d9d804d19f865a6496b82d1cee2a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/period/zipball/280b54d737b4d9d804d19f865a6496b82d1cee2a",
"reference": "280b54d737b4d9d804d19f865a6496b82d1cee2a",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^1.10",
"phpunit/phpunit": "^4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"psr-4": {
"League\\Period\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "[email protected]",
"homepage": "https://github.com/nyamsprod/",
"role": "Developer"
}
],
"description": "A time range immutable value object",
"homepage": "http://period.thephpleague.com",
"keywords": [
"date",
"dateinterval",
"dateperiod",
"datetime",
"interval",
"range",
"time"
],
"time": "2017-11-17T11:28:33+00:00"
},
{
"name": "linkorb/jsmin-php",
"version": "1.0.0",
@@ -3686,7 +3743,9 @@
"prefer-lowest": false,
"platform": {
"php": "^7.1",
"ext-json": "*"
"ext-json": "*",
"ext-libxml": "*",
"ext-simplexml": "*"
},
"platform-dev": [],
"platform-overrides": {
@@ -27,19 +27,19 @@ namespace EP\EpProducts\Command;
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use EP\EpProducts\Service\DatesImportService;
use EP\EpProducts\Service\DateImportService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\CommandController;
use TYPO3\CMS\Extbase\Service\CacheService;
class ProductCommandController extends CommandController
class DateCommandController extends CommandController
{
/**
* @var \EP\EpProducts\Service\DatesImportService
* @var \EP\EpProducts\Service\DateImportService
*/
protected $datesImportService;
protected $dateImportService;
/**
* @var CacheService
@@ -52,18 +52,18 @@ class ProductCommandController extends CommandController
protected $configurationManager;
/**
* @param DatesImportService $datesImportService
* @param DateImportService $importService
* @param CacheService $cacheService
* @param ConfigurationManagerInterface $configurationManager
*/
public function __construct
(
DatesImportService $datesImportService,
DateImportService $importService,
CacheService $cacheService,
ConfigurationManagerInterface $configurationManager
)
{
$this->datesImportService = $datesImportService;
$this->dateImportService = $importService;
$this->cacheService = $cacheService;
$this->configurationManager = $configurationManager;
}
@@ -71,10 +71,10 @@ class ProductCommandController extends CommandController
public function importCommand()
{
$path = GeneralUtility::getFileAbsFileName('fileadmin/xmlexport');
if ($this->datesImportService->checkActiveUpload($path)) {
if ($this->dateImportService->checkActiveUpload($path)) {
$this->outputLine('Import cancelled due to active file upload.');
} else {
$count = $this->datesImportService->import($path);
$count = $this->dateImportService->import($path);
$this->outputLine('%d rows imported.', [ $count ]);
$settings = $this->getSettings();
$resellerPageUid = $settings['resellerExportPageUid'];
@@ -58,7 +58,7 @@ class AjaxCalendarController extends ActionController
*/
public function rangeAction(Hotel $hotel, $year = null, $month = null, $months = 6)
{
list ($begin, $end) = DateUtility::getDateRange($year, $month, $months);
[ $begin, $end ] = DateUtility::getDateRange($year, $month, $months);
$factory = new Calendar();
$factory->getEventManager()->addProvider('contingent', $this->contingentRepository);
@@ -88,4 +88,5 @@ class AjaxCalendarController extends ActionController
'events' => $events,
]);
}
}
@@ -0,0 +1,100 @@
<?php
namespace EP\EpProducts\Controller;
/***************************************************************
*
* Copyright notice
*
* (c) 2016 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 EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Domain\Repository\ContingentRepository;
use EP\EpProducts\Service\ContingentDataService;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class AjaxContingentController extends ActionController
{
/**
* @var \EP\EpProducts\Domain\Repository\ContingentRepository
*/
protected $contingentRepository;
/**
* @var ContingentDataService
*/
protected $contingentDataService;
/**
* @param ContingentRepository $contingentRepository
* @param ContingentDataService $contingentDataService
*/
public function __construct
(
ContingentRepository $contingentRepository,
ContingentDataService $contingentDataService
)
{
parent::__construct();
$this->contingentRepository = $contingentRepository;
$this->contingentDataService = $contingentDataService;
}
public function initializeAction()
{
if ($this->request->hasArgument('dateFrom')) {
$dateFrom = new \DateTime($this->request->getArgument('dateFrom'));
$this->request->setArgument('dateFrom', $dateFrom);
}
if ($this->request->hasArgument('dateTo')) {
$dateTo = new \DateTime($this->request->getArgument('dateTo'));
$this->request->setArgument('dateTo', $dateTo);
}
}
/**
* @param Hotel $hotel
* @param Product $product
* @return string
*/
public function listAction(Hotel $hotel, Product $product)
{
$enabled = $this->contingentRepository->getAvailableContingents($hotel, $product);
return json_encode($enabled);
}
/**
* @param Hotel $hotel
* @param Product $product
* @param \DateTime $dateFrom
* @param \DateTime $dateTo
* @return string
*/
public function roomsAction(Hotel $hotel, Product $product, \DateTime $dateFrom, \DateTime $dateTo)
{
$rooms = $this->contingentDataService->getAvailableRooms($dateFrom, $dateTo, $hotel, $product);
return \json_encode($rooms);
}
}
@@ -35,6 +35,8 @@ use EP\EpProducts\Service\DateService;
use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\ResellerDataService;
use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait;
use EP\EpProducts\Utility\DateUtility;
use League\Period\Period;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
@@ -90,7 +90,13 @@ class AjaxTableController extends ActionController
*
* @return string
*/
public function pricetableAction(Product $product, Hotel $hotel, FilterSettings $filterSettings = null, Date $date = null)
public function pricetableAction
(
Product $product,
Hotel $hotel,
FilterSettings $filterSettings = null,
Date $date = null
)
{
if ($filterSettings === null) {
$forcedConceptUid = (int) $this->settings['forcedConceptUid'];
@@ -28,7 +28,7 @@ namespace EP\EpProducts\Controller;
***************************************************************/
use EP\EpProducts\Service\ContingentImportService;
use EP\EpProducts\Service\DatesImportService;
use EP\EpProducts\Service\DateImportService;
use EP\EpProducts\Service\LogService;
use EP\EpProducts\Service\SnowreportImportService;
use TYPO3\CMS\Backend\View\BackendTemplateView;
@@ -49,14 +49,9 @@ class BackendController extends ActionController
protected $view;
/**
* @var \EP\EpProducts\Service\DatesImportService
* @var \EP\EpProducts\Service\DateImportService
*/
protected $datesImportService;
/**
* @var \EP\EpProducts\Service\ContingentImportService
*/
protected $contingentImportService;
protected $dateImportService;
/**
* @var \EP\EpProducts\Service\SnowreportImportService
@@ -69,23 +64,29 @@ class BackendController extends ActionController
protected $logService;
/**
* @param DatesImportService $datesImportService
* @var ContingentImportService
*/
protected $contingentImportService;
/**
* @param DateImportService $dateImportService
* @param ContingentImportService $contingentImportService
* @param SnowreportImportService $snowreportImportService
* @param SnowreportImportService $snowreportService
* @param LogService $logService
*/
public function __construct
(
DatesImportService $datesImportService,
DateImportService $dateImportService,
ContingentImportService $contingentImportService,
SnowreportImportService $snowreportImportService,
SnowreportImportService $snowreportService,
LogService $logService
)
{
parent::__construct();
$this->datesImportService = $datesImportService;
$this->dateImportService = $dateImportService;
$this->contingentImportService = $contingentImportService;
$this->snowreportImportService = $snowreportImportService;
$this->snowreportImportService = $snowreportService;
$this->logService = $logService;
}
@@ -95,14 +96,14 @@ class BackendController extends ActionController
public function importProductsAction()
{
$path = GeneralUtility::getFileAbsFileName('fileadmin/xmlexport');
if ($this->datesImportService->checkActiveUpload($path)) {
if ($this->dateImportService->checkActiveUpload($path)) {
$this->addFlashMessage(
'Datenexport aus BusProNet aktiv. Import nicht möglich.',
'Import fehlgeschlagen',
AbstractMessage::WARNING
);
} else {
$dateCount = $this->datesImportService->import($path);
$dateCount = $this->dateImportService->import($path);
$this->addFlashMessage($dateCount . ' Termine importiert', 'Import abgechlossen', AbstractMessage::OK);
$resellerPageUid = $this->settings['resellerExportPageUid'];
$this->cacheService->clearPageCache($resellerPageUid);
@@ -153,7 +154,7 @@ class BackendController extends ActionController
header('Expires: 0');
header('Pragma: public');
$fh = @fopen( 'php://output', 'w' );
$fh = @fopen( 'php://output', 'wb' );
foreach ($csv as $data) {
fputcsv($fh, $data, ';');
@@ -135,7 +135,7 @@ class ProductController extends ActionController
$hotel = $this->hotelRepository->findByIdentifier($hotelUid);
}
if ($hotel === null) {
// Forward to hotel list action in case no single hotel can be determined
// Forward to hotel list action in case multiple hotels are assigned to this product
if ($product->getHotels()->count() > 1) {
$arguments = [
'product' => $product,
@@ -149,11 +149,11 @@ class ProductController extends ActionController
$hotel = reset($product->getHotels()->toArray());
}
}
list($productDateRangeFrom, $productDateRangeTo) = $this->filterService->getProductDateRange($product, $filterSettings);
[ $productDateRangeFrom, $productDateRangeTo ] = $this->filterService->getProductDateRange($product, $filterSettings);
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']);
$isProductWithExcludedConcept = ($product->getConcept() !== null) && in_array($product->getConcept()->getUid(), $excludedConceptUids);
$isProductWithExcludedConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(), $excludedConceptUids, false);
$noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids']);
$isProductWithNoInfoConcept = ($product->getConcept() !== null) && in_array($product->getConcept()->getUid(), $noInfoConceptUids);
$isProductWithNoInfoConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(), $noInfoConceptUids, false);
$this->view->assignMultiple([
'filterSettings' => $filterSettings,
'hotel' => $hotel,
@@ -195,7 +195,7 @@ class ProductController extends ActionController
)
{
$hotels = $this->hotelDataService->getList($product);
list ($productDateRangeFrom, $productDateRangeTo) = $this->filterService->getProductDateRange($product, $filterSettings);
[ $productDateRangeFrom, $productDateRangeTo ] = $this->filterService->getProductDateRange($product, $filterSettings);
$this->view->assignMultiple([
'filterSettings' => $filterSettings,
'hotels' => $hotels,
@@ -400,7 +400,7 @@ class FilterSettings implements \JsonSerializable
/**
* @param \DateTime $dateFrom
*/
public function setDateFrom(\DateTime $dateFrom = null)
public function setDateFrom(\DateTimeInterface $dateFrom = null)
{
$this->dateFrom = $dateFrom;
}
@@ -416,7 +416,7 @@ class FilterSettings implements \JsonSerializable
/**
* @param \DateTime $dateTo
*/
public function setDateTo(\DateTime $dateTo = null)
public function setDateTo(\DateTimeInterface $dateTo = null)
{
if ($this->dateFrom !== null && $dateTo !== null && $dateTo < $this->dateFrom) {
$dateTo = $this->dateFrom;
@@ -284,7 +284,7 @@ class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements
*/
public function getBookable()
{
return $this->bookable;
return (bool) $this->bookable;
}
/**
@@ -300,7 +300,7 @@ class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements
*/
public function isDaytrip()
{
return $this->daytrip;
return (bool) $this->daytrip;
}
/**
@@ -308,7 +308,7 @@ class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements
*/
public function setDaytrip($daytrip)
{
$this->daytrip = $daytrip;
$this->daytrip = (bool) $daytrip;
}
/**
@@ -30,6 +30,7 @@ namespace EP\EpProducts\Domain\Repository;
use CalendR\Event\Provider\ProviderInterface;
use EP\EpProducts\Domain\Model\Contingent;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Utility\DbUtility;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -90,4 +91,67 @@ class ContingentRepository extends AbstractRepository implements ProviderInterfa
return $events;
}
/**
* @param Hotel $hotel
* @param Product $product
* @return array
*/
public function getAvailableContingents(Hotel $hotel, Product $product)
{
$qb = DbUtility::getDbConnection()->createQueryBuilder();
$qb
->select('c.date date', 'SUM(c.available) total')
->from('tx_epproducts_domain_model_contingent', 'c')
->innerJoin('c', 'tx_epproducts_domain_model_daytrip', 'd', 'c.date = d.date AND c.hotel_code = d.hotel_code')
->where('c.hotel = :hotelUid')
->andWhere('d.product = :productUid')
->andWhere('c.available > 0')
->setParameters([
'hotelUid' => $hotel->getUid(),
'productUid' => $product->getUid(),
])
->groupBy('c.date')
->orderBy('c.date')
;
return $qb->execute()->fetchAll();
}
/**
* @param \DateTime $dateFrom
* @param \DateTime $dateTo
* @param Hotel $hotel
* @param Product $product
* @return array
*/
public function getAvailableRooms(\DateTime $dateFrom, \DateTime $dateTo, Hotel $hotel, Product $product)
{
$qb = DbUtility::getDbConnection()->createQueryBuilder();
$qb
->select('c.room_code code', 'c.room_label label', 'c.available', 'c.min_price price',
'c.hotel_bus_pro_id hotelBusProId', 'd.bus_pro_id busProId')
->from('tx_epproducts_domain_model_contingent', 'c')
->innerJoin('c', 'tx_epproducts_domain_model_daytrip', 'd', 'c.date = d.date AND c.hotel_code = d.hotel_code')
->where('c.hotel = :hotelUid')
->andWhere('d.product = :productUid')
->andWhere('c.date >= :dateFrom')
->andWhere('c.date <= :dateTo')
->andWhere('c.min_price > 0')
->setParameters([
'hotelUid' => $hotel->getUid(),
'productUid' => $product->getUid(),
'dateFrom' => $dateFrom->format('Y-m-d'),
'dateTo' => $dateTo->format('Y-m-d'),
])
->groupBy('c.room_code')
->orderBy('c.pax')
->addOrderBy('c.min_price')
;
return $qb->execute()->fetchAll();
}
}
@@ -0,0 +1,76 @@
<?php
namespace EP\EpProducts\Service;
/***************************************************************
*
* Copyright notice
*
* (c) 2018 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 EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Domain\Repository\ContingentRepository;
use EP\EpProducts\Utility\BookingUrlUtility;
class ContingentDataService
{
/**
* @var ContingentRepository
*/
protected $contingentRepository;
/**
* @param ContingentRepository $contingentRepository
*/
public function __construct(ContingentRepository $contingentRepository)
{
$this->contingentRepository = $contingentRepository;
}
/**
* @param \DateTime $dateFrom
* @param \DateTime $dateTo
* @param Hotel $hotel
* @param Product $product
* @return array
*/
public function getAvailableRooms(\DateTime $dateFrom, \DateTime $dateTo, Hotel $hotel, Product $product)
{
$data = $this->contingentRepository->getAvailableRooms($dateFrom, $dateTo, $hotel, $product);
$rooms = [];
foreach ($data as $row)
{
$row['bookingUrl'] = BookingUrlUtility::generateUrl([
'bookingId' => $row['busProId'],
'hotelId' => $row['hotelBusProId'],
'dateEnd' => $dateTo->format('Y-m-d'),
]);
$rooms[] = $row;
}
return $rooms;
}
}
@@ -151,7 +151,7 @@ class ContingentImportService implements SingletonInterface
protected function parseContingent(\SimpleXMLElement $xmlContingent)
{
$hotelCode = (string) $xmlContingent['code'];
$sql = 'SELECT uid, groupsch_id FROM tx_epproducts_domain_model_hotel WHERE deleted = 0 AND hidden = 0 AND code = :code';
$sql = 'SELECT uid, code, groupsch_id FROM tx_epproducts_domain_model_hotel WHERE deleted = 0 AND hidden = 0 AND code = :code';
$hotelData = $this->db->fetchAssoc($sql, ['code' => $hotelCode]);
// Skip import in case hotel can't be found
@@ -162,6 +162,7 @@ class ContingentImportService implements SingletonInterface
$this->logger->warning('hotel has no contingents', ['code' => $hotelCode]);
return 0;
} else {
$hotelData['busProId'] = (string) $xmlContingent['idbuspro'];
$roomTypes = $this->parseRoomTypes($xmlContingent->unterbringungen);
return $this->parseRooms($xmlContingent->kapazitaeten, $hotelData, $roomTypes);
}
@@ -179,6 +180,7 @@ class ContingentImportService implements SingletonInterface
$types[(string) $xmlType['idbuspro']] = [
'pax' => (int) $xmlType['pax_max'],
'code' => (string) $xmlType['code'],
'label' => (string) $xmlType['zimmerbezeichnung'],
'ctrl' => (string) $xmlType['code'] === self::ROOMCODE_STATUS,
];
}
@@ -231,12 +233,16 @@ class ContingentImportService implements SingletonInterface
$status = Contingent::STATUS_ONREQUEST;
}
$minPrice = (int) $xmlRoom['abpreis'];
$roomLabel = $roomType['label'];
$this->db->insert(
'tx_epproducts_domain_model_contingent_temp',
[
'pid' => $this->pid,
'hotel' => $hotelData['uid'],
'hotel_code' => $hotelData['code'],
'hotel_bus_pro_id' => $hotelData['busProId'],
'room_code' => $roomCode,
'room_label' => $roomLabel,
'groupsch_id' => $hotelData['groupsch_id'],
'pax' => $pax,
'available' => $availablePax,
@@ -36,7 +36,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Service\CacheService;
class DatesImportService implements SingletonInterface
class DateImportService implements SingletonInterface
{
const CODE_PSEUDOPRICE = 'PDGS';
const CODE_BABYROOM = 'Baby';
@@ -174,24 +174,28 @@ class DatesImportService implements SingletonInterface
public function createTempTables()
{
$this->db->exec('CREATE TABLE IF NOT EXISTS tx_epproducts_domain_model_date_temp LIKE tx_epproducts_domain_model_date');
$this->db->exec('CREATE TABLE IF NOT EXISTS tx_epproducts_domain_model_daytrip_temp LIKE tx_epproducts_domain_model_daytrip');
$this->db->exec('CREATE TABLE IF NOT EXISTS tx_epproducts_domain_model_room_temp LIKE tx_epproducts_domain_model_room');
}
public function clearTables()
{
$this->db->exec('TRUNCATE TABLE tx_epproducts_domain_model_date');
$this->db->exec('TRUNCATE TABLE tx_epproducts_domain_model_daytrip');
$this->db->exec('TRUNCATE TABLE tx_epproducts_domain_model_room');
}
public function copyTables()
{
$this->db->exec('INSERT INTO tx_epproducts_domain_model_date SELECT * FROM tx_epproducts_domain_model_date_temp');
$this->db->exec('INSERT INTO tx_epproducts_domain_model_daytrip SELECT * FROM tx_epproducts_domain_model_daytrip_temp');
$this->db->exec('INSERT INTO tx_epproducts_domain_model_room SELECT * FROM tx_epproducts_domain_model_room_temp');
}
public function clearTempTables()
{
$this->db->exec('TRUNCATE TABLE tx_epproducts_domain_model_date_temp');
$this->db->exec('TRUNCATE TABLE tx_epproducts_domain_model_daytrip_temp');
$this->db->exec('TRUNCATE TABLE tx_epproducts_domain_model_room_temp');
}
@@ -206,7 +210,7 @@ class DatesImportService implements SingletonInterface
$dateCount = 0;
$sql = '
SELECT p.uid uid, p.name product_name, p.bus_pro_id bus_pro_id, p.searchable product_searchable,
SELECT p.uid uid, p.daytrip daytrip, p.name product_name, p.bus_pro_id bus_pro_id, p.searchable product_searchable,
p.detail_page product_detail_page, p.keywords product_keywords, p.teaser product_teaser,
p.feature product_feature, p.subline product_subline, p.country country, p.region region, p.city city,
cnt.name country_name, cnt.code country_code, cnt.keywords country_keywords, r.name region_name,
@@ -224,24 +228,31 @@ class DatesImportService implements SingletonInterface
// Skip import in case product can't be found
if (count($products) === 0) {
$this->logger->warning('product not found', ['code' => $code]);
$this->logger->warning('product not found or daytrip', ['code' => $code]);
return 0;
}
foreach ($products as $product)
{
if (empty($product['bus_pro_id'])) {
$this->db->update(
'tx_epproducts_domain_model_product',
['bus_pro_id' => $busProId],
['uid' => $product['uid']]
);
}
$product['product_fact'] = $this->getTopFactForRecord('tx_epproducts_product_fact_mm', $product['uid']);
$product['region_fact'] = $this->getTopFactForRecord('tx_epproducts_region_fact_mm', $product['region']);
foreach ($xmlProduct->termin as $xmlDate)
{
$dateCount += $this->parseDates($xmlDate, $product);
// Skip import in case product is daytrip
if ($product['daytrip']) {
foreach ($xmlProduct->termin as $xmlDate)
{
$this->parseDaytripDates($xmlDate, $product);
}
} else {
if (empty($product['bus_pro_id'])) {
$this->db->update(
'tx_epproducts_domain_model_product',
['bus_pro_id' => $busProId],
['uid' => $product['uid']]
);
}
$product['product_fact'] = $this->getTopFactForRecord('tx_epproducts_product_fact_mm', $product['uid']);
$product['region_fact'] = $this->getTopFactForRecord('tx_epproducts_region_fact_mm', $product['region']);
foreach ($xmlProduct->termin as $xmlDate)
{
$dateCount += $this->parseDates($xmlDate, $product);
}
}
}
@@ -281,6 +292,32 @@ class DatesImportService implements SingletonInterface
return $dateCount;
}
/**
* @param \SimpleXMLElement $xmlDate
* @param array $product
*/
protected function parseDaytripDates(\SimpleXMLElement $xmlDate, array $product)
{
foreach ($xmlDate->hotel as $xmlHotel)
{
$hotelCode = (string) $xmlHotel->attributes()['code'];
$hotel = $this->hotelMappings[$hotelCode];
$dateStart = \DateTime::createFromFormat('d.m.Y', (string) $xmlDate->attributes()['termin'])->format('Y-m-d');
$this->db->insert(
'tx_epproducts_domain_model_daytrip_temp',
[
'product' => $product['uid'],
'date' => $dateStart,
'bus_pro_id' => (int) $xmlDate->attributes()['idbuspro'],
'code' => (string) $xmlDate->attributes()['code'],
'hotel' => $hotel['uid'],
'hotel_bus_pro_id' => (int) $xmlHotel->attributes()['idbuspro'],
'hotel_code' => $hotelCode,
]
);
}
}
/**
* @param array $product
* @param \SimpleXMLElement $xmlDate
@@ -65,7 +65,7 @@ class FilterService implements SingletonInterface
*/
public function getDefaultFilterSettings($forcedConceptUid = null, $respectBookableDaterange = true)
{
list($dateFrom, $dateTo) = $this->constrainDateRange($this->getDateRange());
[ $dateFrom, $dateTo ] = $this->constrainDateRange($this->getDateRange());
if (!$respectBookableDaterange) {
$dateFrom = new \DateTime('now');
}
@@ -160,12 +160,12 @@ class FilterService implements SingletonInterface
}
// Hotel types
if (!in_array($row['hotelType'], $hotelTypes)) {
if (!\in_array($row['hotelType'], $hotelTypes, false)) {
$hotelTypes[] = $row['hotelType'];
}
// Room types
if (!in_array($row['roomType'], $roomTypes)) {
if (!\in_array($row['roomType'], $roomTypes, false)) {
$roomTypes[] = $row['roomType'];
}
@@ -174,7 +174,7 @@ class FilterService implements SingletonInterface
'uid' => $row['boardUid'],
'label' => $row['boardType'],
];
if (!in_array($boardType, $boardTypes)) {
if (!\in_array($boardType, $boardTypes, false)) {
$boardTypes[] = $boardType;
}
@@ -260,7 +260,7 @@ class FilterService implements SingletonInterface
*/
public function constrainDateRange(array $dateRange, FilterSettings $filterSettings = null)
{
list ($dateFrom, $dateTo) = $dateRange;
[ $dateFrom, $dateTo ] = $dateRange;
if ($filterSettings !== null) {
$filterSettingsDateFrom = $filterSettings->getDateFrom();
if ($filterSettingsDateFrom !== null && $filterSettingsDateFrom > $dateFrom) {
@@ -279,7 +279,7 @@ class FilterService implements SingletonInterface
*/
public function sanitizeDateRange(FilterSettings $filterSettings)
{
list($validDateFrom, $validDateTo) = $this->getDateRange();
[ $validDateFrom, $validDateTo ] = $this->getDateRange();
$dateFrom = $filterSettings->getDateFrom();
$dateTo = $filterSettings->getDateTo();
if ($dateFrom !== null && $dateTo !== null && $dateFrom >= $dateTo) {
@@ -305,7 +305,7 @@ class FilterService implements SingletonInterface
{
if ($conceptUid) {
$conceptUids = $filterSettings->getConceptUids();
if (!in_array($conceptUid, $conceptUids)) {
if (!\in_array($conceptUid, $conceptUids, false)) {
$conceptUids[] = (int) $conceptUid;
$filterSettings->setConceptUids($conceptUids);
}
@@ -27,20 +27,20 @@ namespace EP\EpProducts\Task;
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use EP\EpProducts\Service\DatesImportService;
use EP\EpProducts\Service\DateImportService;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
class ImportProductsTask extends AbstractTask
class ImportDatesTask extends AbstractTask
{
public function execute()
{
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** @var \EP\EpProducts\Service\DatesImportService $importService */
$importService = $objectManager->get(DatesImportService::class);
/** @var \EP\EpProducts\Service\DateImportService $importService */
$importService = $objectManager->get(DateImportService::class);
$path = GeneralUtility::getFileAbsFileName('fileadmin/xmlexport');
if ($importService->checkActiveUpload($path)) {
$message = GeneralUtility::makeInstance(FlashMessage::class, 'Import aborted due to active file upload', '', FlashMessage::WARNING);
@@ -38,9 +38,9 @@ class ImportSnowreportTask extends AbstractTask
{
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$imporService = $objectManager->get(SnowreportImportService::class);
$imporService->downloadReportsXml();
$imporService->importSnowReportData();
$snowreportService = $objectManager->get(SnowreportImportService::class);
$snowreportService->downloadReportsXml();
$snowreportService->importSnowReportData();
return true;
}
}
@@ -24,10 +24,10 @@ return [
'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg',
],
'interface' => [
'showRecordFieldList' => 'date,pax,available,status,min_price,hotel,room_code,groupsch_id',
'showRecordFieldList' => 'date,pax,available,status,min_price,hotel,room_code,room_label,groupsch_id',
],
'types' => [
'1' => ['showitem' => 'date, pax, available, status, min_price, hotel, room_code, groupsch_id'],
'1' => ['showitem' => 'date, pax, available, status, min_price, hotel, room_code, room_label, groupsch_id'],
],
'palettes' => [],
'columns' => [
@@ -203,5 +203,14 @@ return [
'eval' => 'trim'
],
],
'room_label' => [
'exclude' => 0,
'label' => 'Zimmerbezeichnung',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim'
],
],
],
];
@@ -118,6 +118,10 @@ tx_epproducts_ajax_json {
AjaxCalendar {
1 = range
}
AjaxContingent {
1 = list
2 = rooms
}
AjaxWatchlist {
1 = list
}
@@ -6,8 +6,8 @@ if (!defined('TYPO3_MODE')) {
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][$_EXTKEY] =
\EP\EpProducts\Hook\Tcemain::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers']['ep_products_import'] =
\EP\EpProducts\Command\ProductCommandController::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers']['ep_products_dates'] =
\EP\EpProducts\Command\DateCommandController::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers']['ep_products_snow_report'] =
\EP\EpProducts\Command\SnowreportCommandController::class;
@@ -15,9 +15,9 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers']['ep_p
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers']['ep_products_contingent'] =
\EP\EpProducts\Command\ContingentCommandController::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\EP\EpProducts\Task\ImportProductsTask::class] = [
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\EP\EpProducts\Task\ImportDatesTask::class] = [
'extension' => $_EXTKEY,
'title' => 'EP Produkt Import',
'title' => 'EP Reisedaten Import',
'description' => 'Importiert Reisedaten aus busProNet',
];
@@ -327,9 +327,9 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\EP\EpProducts\T
'AjaxDate' => 'dates',
'AjaxFilterpanel' => 'autocomplete',
'AjaxSearchbar' => 'options,reset',
'AjaxContent' => 'country,region,city,hotel,product,journey,webcam',
'AjaxContingent' => 'list,rooms',
'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable',
'AjaxCalendar' => 'range',
'AjaxCalendar' => 'range,contingents,availableRooms',
'AjaxWatchlist' => 'list',
],
[
@@ -339,7 +339,8 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\EP\EpProducts\T
'AjaxSearchbar' => 'options,reset',
'AjaxContent' => 'webcam',
'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable',
'AjaxCalendar' => 'range',
'AjaxCalendar' => 'range,contingents,availableRooms',
'AjaxContingent' => 'list,rooms',
'AjaxWatchlist' => 'list',
]
);
@@ -947,7 +947,10 @@ CREATE TABLE tx_epproducts_domain_model_contingent (
available int(11) unsigned DEFAULT '0' NOT NULL,
date date DEFAULT '0000-00-00',
hotel int(11) unsigned DEFAULT '0',
hotel_code varchar(64) DEFAULT '' NOT NULL,
hotel_bus_pro_id varchar(64) DEFAULT '' NOT NULL,
room_code varchar(64) DEFAULT '' NOT NULL,
room_label varchar(64) DEFAULT '' NOT NULL,
status tinyint(4) unsigned DEFAULT '0' NOT NULL,
min_price int(11) DEFAULT '0' NOT NULL,
groupsch_id varchar(8) DEFAULT '' NOT NULL,
@@ -982,6 +985,51 @@ CREATE TABLE tx_epproducts_domain_model_contingent (
);
#
# Table structure for table 'tx_epproducts_domain_model_daytrip'
#
CREATE TABLE tx_epproducts_domain_model_daytrip (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
product int(11) unsigned DEFAULT '0',
date date DEFAULT '0000-00-00',
bus_pro_id varchar(64) DEFAULT '' NOT NULL,
code varchar(64) DEFAULT '' NOT NULL,
hotel int(11) unsigned DEFAULT '0',
hotel_bus_pro_id varchar(64) DEFAULT '' NOT NULL,
hotel_code varchar(64) DEFAULT '' 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,
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),
KEY t3ver_oid (t3ver_oid,t3ver_wsid),
KEY language (l10n_parent,sys_language_uid),
);
#
# Table structure for table 'tx_epproducts_domain_model_badge'
#
@@ -120,4 +120,5 @@ config {
compressBody = 1
typolinkCheckRootline = 1
typolinkEnableLinksAcrossDomains = 1
sendCacheHeaders = 1
}
@@ -0,0 +1,195 @@
<template>
<div class="date-select-wrapper">
<div class="ep-headbar">{{ headerLabel }}</div>
<div class="row">
<div class="col-xs-12 col-md-6">
<div class="date-select">
<div class="date-select__picker">
<div class="daterange__panel">
<input ref="field" type="hidden">
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6">
<table class="table table-striped pricetable" v-show="availableRooms.length">
<thead>
<tr>
<th>Zimmerart</th>
<th>Preis</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="entry of availableRooms">
<td>{{entry.label}}</td>
<td>ab {{entry.price * selectedNumberOfDays}} &euro;</td>
<td>
<a :href="entry.bookingUrl" target="_blank"
class="button button--small button--action">Buchen</a>
</td>
</tr>
</tbody>
</table>
<div v-show="loading" class="loader"><p>Loading...</p></div>
<div v-show="message">{{message}}</div>
</div>
</div>
</div>
</template>
<script>
import $ from 'jquery'
import Vue from 'vue'
import flatpickr from 'flatpickr'
import { German } from 'flatpickr/dist/l10n/de'
import dayjs from 'dayjs'
import 'dayjs/locale/de'
import DatesTable from './DatesTable.vue'
export default {
components: {
DatesTable
},
data () {
return {
loading: false,
picker: null,
displayedFrom: dayjs().startOf('month'),
displayedTo: dayjs().endOf('month'),
selectedFrom: null,
selectedTo: null,
availableDates: [],
availableRooms: []
}
},
props: {
hotelName: {
type: String,
required: true
},
hotelUid: {
type: Number,
required: true
},
productUid: {
type: Number,
required: true
},
minDate: {
type: Date,
default () {
return new Date()
}
},
maxDate: {
type: Date,
default () {
return new Date(dayjs().add(1, 'year'))
}
},
contingentEndpointUri: {
type: String,
required: true
},
roomsEndpointUri: {
type: String,
required: true
},
argumentPrefix: {
type: String,
required: true
}
},
computed: {
headerLabel () {
let label = 'Buchungsoptionen';
if (this.hotelName) {
label += ' ' + this.hotelName;
}
if (this.selectedFrom && this.selectedTo) {
label += ' ' + this.selectedFrom.format('DD.MM.YY') + ' - ' + this.selectedTo.format('DD.MM.YY');
label += ' (' + this.selectedNumberOfDays + ' Nächte)';
}
return label;
},
selectedNumberOfDays () {
if (this.selectedFrom === null || this.selectedTo === null) {
return 0;
}
return this.selectedTo.diff(this.selectedFrom, 'days');
},
message () {
if (this.loading) {
return '';
}
if (this.selectedFrom === null || this.selectedTo === null) {
return 'Bitte einen Zeitraum auswählen.';
}
if (this.selectedTo.diff(this.selectedFrom, 'days') < 2) {
return 'Bitte mindestens zwei Nächte auswählen';
}
if (this.availableRooms.length === 0) {
return 'Im gewählten Zeitraum sind leider keine Zimmer verfügbar.';
}
return '';
}
},
methods: {
onDateSelected (date) {
this.selectedDate = dayjs(date);
},
fetchEnabledDates () {
let query = {};
query[this.argumentPrefix + '[hotel]'] = this.hotelUid;
query[this.argumentPrefix + '[product]'] = this.productUid;
this.loading = true;
$.post(this.contingentEndpointUri, query, (json) => {
this.loading = false;
this.availableDates = json;
const enabledDates = [];
for (let entry of this.availableDates) {
enabledDates.push(entry.date);
}
this.picker.set('enable', enabledDates);
}, 'json');
},
fetchAvailableRooms () {
let query = {};
query[this.argumentPrefix + '[hotel]'] = this.hotelUid;
query[this.argumentPrefix + '[product]'] = this.productUid;
query[this.argumentPrefix + '[dateFrom]'] = this.selectedFrom.format('YYYY-MM-DD');
query[this.argumentPrefix + '[dateTo]'] = this.selectedTo.format('YYYY-MM-DD');
this.loading = true;
$.post(this.roomsEndpointUri, query, (json) => {
this.loading = false;
this.availableRooms = json;
}, 'json');
}
},
mounted () {
this.picker = $(this.$refs.field).flatpickr({
mode: 'range',
dateFormat: 'Y-m-d',
locale: German,
inline: true,
minDate: new Date(this.minDate),
maxDate: new Date(this.maxDate),
onChange: (selectedDates) => {
this.availableRooms = [];
if (selectedDates.length === 2) {
this.selectedFrom = dayjs(selectedDates[0]);
this.selectedTo = dayjs(selectedDates[1]);
if (this.selectedTo.diff(this.selectedFrom, 'days') > 1) {
this.fetchAvailableRooms();
}
}
}
});
Vue.nextTick(() => {
this.fetchEnabledDates();
});
}
}
</script>
@@ -1,7 +1,9 @@
<template>
<div class="dates-table-wrapper">
<div v-if="bookable" class="ep-headbar">{{ headerLabel }}</div>
<div v-if="!bookable" class="ep-headbar">Termine &amp; Leistungen</div>
<template v-if="showHeader">
<div v-if="bookable" class="ep-headbar">{{ headerLabel }}</div>
<div v-if="!bookable" class="ep-headbar">Termine &amp; Leistungen</div>
</template>
<div class="dates-table__pricetoggle" v-if="hasSurcharge || hasDiscount">
<a href="#" v-if="hasSurcharge" v-on:click.prevent="toggleBus">
<template v-if="addBus">
@@ -127,6 +129,10 @@
altLabel: {
type: String,
default: null
},
showHeader: {
type: Boolean,
default: true
}
},
data () {
@@ -1,6 +1,6 @@
<template>
<div class="price-table-wrapper">
<div class="row ep-headbar">
<div class="row ep-headbar" v-if="showHeader">
<div class="col-xs-6">Buchungsoptionen</div>
<div class="col-xs-6 text-right">{{dateLabel}}</div>
</div>
@@ -130,6 +130,10 @@
singleDate: {
type: Boolean,
default: false
},
showHeader: {
typoe: Boolean,
default: true
}
},
data () {
@@ -6,6 +6,7 @@
import PriceTable from './PriceTable.vue'
import ServicesIncluded from './ServicesListIncluded.vue'
import ServicesOptional from './ServicesListOptional.vue'
import DateSelect from './DateSelect.vue'
import DaterangeSelect from './DaterangeSelect.vue'
import AjaxContent from './AjaxContent.vue'
import Calendar from './Calendar.vue'
@@ -21,6 +22,7 @@
PriceTable,
ServicesIncluded,
ServicesOptional,
DateSelect,
DaterangeSelect,
AjaxContent,
Calendar,
@@ -47,7 +49,42 @@
singleDate: false
}
},
props: [ 'filterSettings', 'argumentPrefix', 'uris', 'hotelFirst', 'fbPixelData', 'watchlist' ],
props: {
filterSettings: {
type: Object,
default () {
return {}
}
},
argumentPrefix: {
type: String,
required: true
},
uris: {
type: Object,
required: true
},
hotelFirst: {
type: Boolean,
default: false
},
fbPixelData: {
type: Object,
default () {
return {}
}
},
watchlist: {
type: Array,
default () {
return []
}
},
daytrip: {
type: Number,
default: 0
}
},
computed: {
dateLabel () {
return this.dateStart + ' - ' + this.dateEnd;
@@ -79,10 +116,36 @@
this.loading = false;
EventBus.$emit('tableLoaded');
}, 'json');
},
loadSelectableDates () {
this.loading = true;
this.datesShow = true;
this.priceTableShow = false;
this.detailShow = false;
let query = {};
query[this.argumentPrefix + '[product]'] = this.filterSettings;
$.post(this.uris['dateslist'], query, (json) => {
if (json.dates.length === 1) {
this.singleDate = true;
let dateRow = json.dates[0];
this.loadPriceTableView(dateRow.dateUid);
} else {
this.singleDate = false;
this.datesRows = json.dates;
this.bookable = json.bookable;
this.altLabel = json.altLabel;
this.altProductLink = json.altProductLink;
this.loading = false;
}
}, 'json');
},
loadDatesView (scroll = true) {
this.loadDates();
if (this.daytrip) {
this.loading = false;
this.datesShow = true;
} else {
this.loadDates();
}
this.activeSection = 'dates';
if (scroll) {
this.scrollToMain();
@@ -13,13 +13,16 @@
<product-detail
:filter-settings='<v:format.json.encode>{filterSettings}</v:format.json.encode>'
:uris='{
dates: "<f:format.raw>{ep:uri.ajax(action: 'dates', controller: 'AjaxDate', pageUid: settings.defaultAjaxUid, arguments: '{product: product, hotel: hotel}', format: 'json')}</f:format.raw>",
pricetable: "<f:format.raw>{ep:uri.ajax(action: 'pricetable', controller: 'AjaxTable', pageUid: settings.defaultAjaxUid, arguments: '{product: product, hotel: hotel}', format: 'json')}</f:format.raw>"
dates: "<f:format.raw>{ep:uri.ajax(action: 'dates', controller: 'AjaxDate', arguments: '{product: product, hotel: hotel}', format: 'json', noCacheHash: 1)}</f:format.raw>",
contingents: "<f:format.raw>{ep:uri.ajax(action: 'list', controller: 'AjaxContingent', format: 'json', noCacheHash: 1)}</f:format.raw>",
rooms: "<f:format.raw>{ep:uri.ajax(action: 'rooms', controller: 'AjaxContingent', format: 'json', noCacheHash: 1)}</f:format.raw>",
pricetable: "<f:format.raw>{ep:uri.ajax(action: 'pricetable', controller: 'AjaxTable', arguments: '{product: product, hotel: hotel}', format: 'json', noCacheHash: 1)}</f:format.raw>"
}'
argument-prefix='<ep:argumentPrefix pluginName="Ajax" />'
:hotel-first="{f:if(condition: settings.hotelOnTop, then: 'true', else: 'false')}"
:fb-pixel-data="{ productUid: <f:format.raw>{product.uid}</f:format.raw>, nameInternal: '<f:format.raw>{product.nameInternal}</f:format.raw>' }"
:watchlist="watchlist"
:daytrip="{product.daytrip -> v:variable.convert(type: 'int')}"
inline-template>
<div class="product-detail-wrapper">
<div class="row">
@@ -128,23 +131,37 @@
</div>
</f:if>
</f:if>
<template v-if="available">
<dates-table :dates-rows="datesRows"
:alt-product-link="altProductLink"
:alt-label="altLabel"
hotel-name="{hotel.name}"
:bookable="bookable"
></dates-table>
</template>
<f:if condition="{isProductWithNoInfoConcept}">
<f:if condition="{product.daytrip}">
<f:then>
<date-select
hotel-name="{hotel.name}"
:hotel-uid="{hotel.uid}"
:product-uid="{product.uid}"
:contingent-endpoint-uri="uris.contingents"
:rooms-endpoint-uri="uris.rooms"
:argument-prefix="argumentPrefix"
></date-select>
</f:then>
<f:else>
<template v-else>
<div class="alert alert-info">
Die von dir gewählte Reise ist aktuell nicht buchbar, aber wir helfen dir gerne dabei,
eine andere Reise zu finden. Ruf uns an unter <a href="tel:{settings.phoneNumber -> v:format.pregReplace(pattern: '/[\-\s]/', replacement: '')}" title="Service Telefon anrufen" rel="nofollow">{settings.phoneNumber}</a>
oder schreibe an <f:link.email email="[email protected]"/>.
</div>
<template v-if="available">
<dates-table :dates-rows="datesRows"
:alt-product-link="altProductLink"
:alt-label="altLabel"
hotel-name="{hotel.name}"
:bookable="bookable"
></dates-table>
</template>
<f:if condition="{isProductWithNoInfoConcept}">
<f:else>
<template v-else>
<div class="alert alert-info">
Die von dir gewählte Reise ist aktuell nicht buchbar, aber wir helfen dir gerne dabei,
eine andere Reise zu finden. Ruf uns an unter <a href="tel:{settings.phoneNumber -> v:format.pregReplace(pattern: '/[\-\s]/', replacement: '')}" title="Service Telefon anrufen" rel="nofollow">{settings.phoneNumber}</a>
oder schreibe an <f:link.email email="[email protected]"/>.
</div>
</template>
</f:else>
</f:if>
</f:else>
</f:if>
<f:render partial="Product/Details" arguments="{_all}"/>
@@ -152,7 +169,8 @@
<!-- /Dates -->
<!-- Prices -->
<div v-cloak v-show="!loading && priceTableShow">
<price-table :price-table-rows="priceTableRows" :services-included="servicesIncluded" :date-label="dateLabel" :single-date="singleDate"></price-table>
<price-table :price-table-rows="priceTableRows" :services-included="servicesIncluded"
:date-label="dateLabel" :single-date="singleDate"></price-table>
<f:render partial="Product/Details" arguments="{_all}"/>
</div>
<!-- /Prices -->
+1
View File
@@ -81,6 +81,7 @@ $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl'] = [
'date' => 'AjaxDate',
'table' => 'AjaxTable',
'calendar' => 'AjaxCalendar',
'contingent' => 'AjaxContingent',
'watchlist' => 'AjaxWatchlist',
]
],