<?php
/**
* Elements DeMI
*
* This source file is available under the elements DeMI license version 1
*
* @copyright Copyright (c) elements.at New Media Solutions GmbH (https://www.elements.at/)
* @license elements DeMI Lizenz Version 1 (https://www.elements.at/de/demi-lizenz)
*/
namespace Elements\Demi\Deskline\DataObject\Adapter;
use Carbon\Carbon;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ParameterType;
use Elements\Demi\DataObject\Adapter\AbstractAccommodationProduct;
use Elements\Demi\Deskline\Config;
use Elements\Demi\Deskline\Constant\DescriptionInterface;
use Elements\Demi\Deskline\Constant\DocumentInterface;
use Elements\Demi\Helper;
use Elements\Demi\Vacancies\Dependency\VacancyDependencyContainer;
use Exception;
use Pimcore;
use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\CartCheckoutData;
use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
use Pimcore\Db;
use Pimcore\Model\DataObject\Fieldcollection\Data\DemiPeriod;
class AccommodationProduct extends AbstractAccommodationProduct implements DocumentInterface, DescriptionInterface
{
protected int|float|null $cheapestStdPrice = null;
protected array $defaultDescriptionType = [self::DESCRIPTION_PRODUCT_DESCRIPTION];
protected array $defaultDocumentTypes = [self::DOCUMENT_TYPE_SERVICE_PROVIDER, self::DOCUMENT_TYPE_PRODUCT, self::DOCUMENT_TYPE_PRODUCT_GROUP, self::DOCUMENT_TYPE_SERVICE];
public function getImageDocuments(mixed $types = null, ?Carbon $filterDate = null, bool $withChilds = false): array
{
if ($types === null) {
$types = $this->defaultDocumentTypes;
}
if ($this->object->getProductType() === 'Package') {
return [];
}
return parent::getImageDocuments($types, $filterDate);
}
public function getDefaultMeals(?Carbon $filterDate = null): array
{
$defaults = [];
$priceTemplates = $this->object->getPriceTemplatesForMeals($filterDate);
if ($priceTemplates) {
foreach ($priceTemplates as $priceTemplate) {
if (!empty($priceTemplate->getDefaultMealType())) {
$defaults[] = $priceTemplate->getDefaultMealType();
}
}
}
return $defaults;
}
/**
* getall valid Mealcodes
*/
public function getValidMeals(int $nights = 0, ?Carbon $filterDate = null): array
{
$mealCodes = [];
$priceTemplates = $this->object->getPriceTemplates();
$dummy = new \Elements\Demi\Model\PriceTemplate();
$classid = $dummy->getClassId();
$priceTemplatesIds = [];
foreach ($priceTemplates as $templates) {
$priceTemplatesIds[] = $templates->getId();
}
$dummy = new DemiPeriod();
$fieldcollname = $dummy->getType();
$result = [];
$builder = Db::get()->createQueryBuilder();
$priceTemplateAdd = '';
if (!empty($priceTemplatesIds)) {
$priceTemplateAdd = $builder->expr()->in('o_id', ':templateIds');
}
if ($filterDate) {
$result = $builder
->select('objectCollection.o_id')
->from('object_collection_' . $fieldcollname . '_' . $classid, 'objectCollection')
->where(
$builder->expr()->and(
$builder->createNamedParameter($filterDate->getTimestamp(), ParameterType::INTEGER) . ' BETWEEN objectCollection.dateFrom AND objectCollection.dateTo',
$priceTemplateAdd
)
)
->setParameter(':templateIds', $priceTemplatesIds, Connection::PARAM_INT_ARRAY)
->execute()->fetchAll();
}
if (count($result) <= 0) {
$builder = Db::get()->createQueryBuilder();
$result = $builder
->select('objectClass.o_id')
->from('object_' . $classid, 'objectClass')
->where(
$builder->expr()->and(
$priceTemplateAdd,
'objectClass.name = ' . $builder->createNamedParameter('Standard')
)
)
->setParameter(':templateIds', $priceTemplatesIds, Connection::PARAM_INT_ARRAY)
->execute()->fetchAll();
}
$template = \Elements\Demi\Model\PriceTemplate::getById((int) $result[0]['o_id']);
if ($template !== null) {
$mTypes = $template->getMealTypes();
if ($mTypes !== null) {
foreach ($template->getMealTypes() as $mealType) {
if ($mealType->getNights() <= $nights) {
$dummyCode = $mealType->getMealType();
$mealCodes[$dummyCode->getFid()] = $dummyCode;
}
}
}
}
return $mealCodes;
}
/**
* Wrapper function to get an array of mealCodes instead of the mealType object
* will be removed in future and replaced with _getMeals()
* kept until the mealCode array is unwrestled in the view-code
*
*@deprecated
*/
public function getMeals(int $nights = 0, ?Carbon $filterDate = null): array
{
$mealCodes = [];
$meals = $this->object->_getMeals($nights, $filterDate);
foreach ($meals as $meal) {
$mealCodes[] = $meal->getFid();
}
return $mealCodes;
}
public function _getMeals(int $nights, ?Carbon $filterDate = null): array
{
$meals = [];
$priceTemplates = $this->object->getPriceTemplatesForMeals($filterDate);
if ($priceTemplates) {
foreach ($priceTemplates as $priceTemplate) {
if (!empty($priceTemplate->getMealTypes())) {
foreach ($priceTemplate->getMealTypes() as $mealType) {
if ($mealType->getNights() >= $nights) {
$meals[] = $mealType->getMealType();
}
}
}
}
}
return $meals;
}
public function getPriceTemplatesForMeals(bool $noFilter = false, ?Carbon $filterDate = null): \Elements\Demi\Model\PriceTemplate
{
$priceTemplates = $this->object->getPriceTemplates();
if (!$noFilter && is_array($priceTemplates) && !Pimcore::inAdmin() && !Config::getInstance()->isImporting()) {
if (!$filterDate) {
$nowTst = time();
} else {
$nowTst = $filterDate->getTimestamp();
}
$priceTemplates = array_filter($priceTemplates, static function ($object) use ($nowTst) {
return $nowTst >= $object->getValidFrom()->getTimestamp();
});
}
return $priceTemplates;
}
public function getPriceForParams(array $params): float
{
// dateFrom Carbon
$mandatory = ['dateFrom', 'occupancy'];
foreach ($mandatory as $key) {
if (!isset($params[$key])) {
return 0;
}
}
$price = 0;
$priceTemplates = $this->object->getPriceTemplates();
$useTemplate = null;
$stdTemplate = null;
$today = new Carbon();
foreach ($priceTemplates as $template) {
if ($template->getValidFrom()->getTimestamp() > $today->getTimestamp()) {
continue;
}
$periods = $template->getPeriods();
if ($template->getIsStandard()) {
$stdTemplate = $template;
continue;
}
if (!empty($periods)) {
foreach ($periods as $period) {
if ($period->getDateFrom()->getTimestamp() <= $params['dateFrom']->getTimestamp() && $period->getDateTo()->getTimestamp() >= $params['dateFrom']->getTimestamp()) {
$useTemplate = $template;
break 2;
}
}
}
}
if (!$useTemplate) {
$useTemplate = $stdTemplate;
}
if (!$useTemplate) {
return 0;
}
$prices = $useTemplate->getPrices();
if (!empty($prices)) {
foreach ($useTemplate->getPrices() as $priceColl) {
if ($priceColl->getOccupancyFrom() <= $params['occupancy'] && $priceColl->getOccupancyTo() >= $params['occupancy']) {
$price = $priceColl->getPrice();
break;
}
}
}
return $price;
}
public function getService(): ?\Elements\Demi\Model\AccommodationService
{
$service = $this->object->getParent();
if($service instanceof \Elements\Demi\Model\AccommodationService){
return $service;
} else {
//this is performance killing legacy code and should only be used as last resort in case the object tree is messed up
$list = \Elements\Demi\Model\AccommodationService::getList(['condition' => 'products LIKE ' . Db::get()->quote('%,' . $this->object->getId() . ',%')]);
return Helper::first($list->getObjects());
}
}
/**
* called by default CommitOrderProcessor to get the product name to store it in the order item
* should be overwritten in mapped sub classes of product classes
*/
public function getOSName(): string
{
return $this->object->getName();
}
/**
* called by default CommitOrderProcessor to get the product number to store it in the order item
* should be overwritten in mapped sub classes of product classes
*/
public function getOSProductNumber(): string
{
return $this->object->getFid();
}
/**
* defines the name of the price system for this product.
* there should either be a attribute in pro product object or
* it should be overwritten in mapped sub classes of product classes
*/
public function getPriceSystemName(): string
{
return 'deskline';
}
/**
* defines if product is included into the product index. If false, product doesn't appear in product index.
*/
public function getOSDoIndexProduct(): bool
{
return false;
}
/**
* returns if product is active.
* there should either be a attribute in pro product object or
* it should be overwritten in mapped sub classes of product classes in case of multiple criteria for product active state
*/
public function isActive(): bool
{
return $this->object->getActive();
}
/**
* returns array of categories.
* has to be overwritten either in pimcore object or mapped sub class.
*/
public function getCategories(): array
{
return [];
}
public function setDesklinePrice($price): void
{
$env = Factory::getInstance()->getEnvironment();
$prices = $env->getDesklineProductPrices();
//TODO CHECK IF EXISTS....
$prices[$this->object->getId()] = $price;
$env->setDesklineProductPrices($prices);
$env->save();
}
//TODO use trait for additional and accommodation Product
public function getDesklinePrice()
{
$env = Factory::getInstance()->getEnvironment();
$data = CartCheckoutData::getByKeyCartId('p_' . $this->object->getId(), $env->getCartId());
$stdObj = unserialize($data->getData());
if (! is_object($stdObj) || empty($stdObj->price)) {
throw new Exception('No Price Set for Product ' . $this->object->getId());
}
return $stdObj->price;
}
//TODO obsolete
public function getDesklineQuantity()
{
$env = Factory::getInstance()->getEnvironment();
$quantities = $env->getDesklineProductQuantities();
return $quantities[$this->object->getId()] ?? 1;
}
public function getCheapestStandardPrice(VacancyDependencyContainer $dc): float|int|null
{
if ($this->cheapestStdPrice <= 0 || $this->cheapestStdPrice === null) {
$query = '
SELECT bp.price as minPrice, pt.rule type, pt.nights nights
FROM ' . $dc->getDBTableName('price_template') . ' pt
INNER JOIN
' . $dc->getDBTableName('price_template_base_prices') . ' bp
ON bp.price_template_id = pt.id
WHERE pt.product_id = ' . $this->object->getId() . '
AND pt.stdOccupancy BETWEEN bp.occupancyFrom AND bp.occupancyTo
AND price > 0
order by minPrice
limit 1
';
$result = $dc->getDb()->fetchAllAssociative($query);
$this->cheapestStdPrice = 0;
if (!empty($result)) {
$this->cheapestStdPrice = $result[0];
}
}
return $this->cheapestStdPrice;
}
/**
* @depricated
*/
public function getMinPriceFromStandardTemplate()
{
$templates = $this->object->getPriceTemplates();
$price = null;
if (!empty($templates)) {
$stdAdults = $this->object->getAdultsStd();
foreach ($templates as $template) {
if (strtolower($template->getName()) === 'standard') {
$prices = $template->getPrices();
if (!empty($prices)) {
foreach ($prices as $price) {
if ($stdAdults >= $price->getOccupancyFrom() && $stdAdults <= $price->getOccupancyTo()) {
$price = $price->getPrice();
break;
}
}
}
break;
}
}
}
return $price;
}
public function getPackageMaster(): Pimcore\Model\DataObject\AbstractObject|\Elements\Demi\Model\HousePackageMaster|Pimcore\Model\DataObject\Concrete|null
{
$dummy = new \Elements\Demi\Model\HousePackageMaster();
$classId = $dummy->getClassId();
$builder = Db::get()->createQueryBuilder();
$result = $builder
->select('objectRelations.src_id')
->from('object_relations_' . $classId, 'objectRelations')
->where('objectRelations.dest_id = ' . $builder->createNamedParameter($this->object->getId(), ParameterType::INTEGER))
->execute()->fetchAll();
$hpm = null;
if (is_array($result) && count($result) === 1) {
$hpm = \Elements\Demi\Model\HousePackageMaster::getById($result[0]['src_id']);
} elseif (is_array($result)) {
foreach ($result as $data) {
$hpm = \Elements\Demi\Model\HousePackageMaster::getById($data['src_id']);
if ($hpm && $hpm->isPublished()) {
break;
}
}
}
return $hpm;
}
}