1236 lines
78 KiB
PHP
1236 lines
78 KiB
PHP
<?php
|
||
|
||
|
||
namespace App\Http\Controllers\V1;
|
||
|
||
use App\Console\Commands\CurrencyRates\CurrencyRatesService;
|
||
use App\Core\Service\ChannelManagerPropertyMappingService;
|
||
use App\Core\Service\CompetitorPriceAnalysisService;
|
||
use App\Core\Service\CurrencyService;
|
||
use App\Core\Service\PropertyChannelCouponService;
|
||
use App\Core\Service\PropertyChannelMappingService;
|
||
use App\Core\Service\PropertyPromotionService;
|
||
use App\Core\Service\PropertyRoomRatePriceService;
|
||
use App\Exceptions\ApiErrorException;
|
||
use App\Export\PropertyCompetitorExport;
|
||
use App\Http\Controllers\Controller;
|
||
use GuzzleHttp\Client;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Carbon;
|
||
use Illuminate\Support\Facades\Cache;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Input;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Exception;
|
||
use Maatwebsite\Excel\Facades\Excel;
|
||
|
||
class CompetitorPriceAnalysisController extends Controller
|
||
{
|
||
|
||
private $params;
|
||
private $tripAdvisorCookie;
|
||
private $competitorPriceAnalysisService;
|
||
private $maxCompetitorPropertyAdd;
|
||
|
||
public function __construct(
|
||
CompetitorPriceAnalysisService $competitorPriceAnalysisService,
|
||
CurrencyService $currencyService,
|
||
PropertyChannelMappingService $propertyChannelMappingService,
|
||
PropertyRoomRatePriceService $propertyRoomRatePriceService,
|
||
PropertyChannelCouponService $propertyChannelCouponService,
|
||
PropertyPromotionService $propertyPromotionService,
|
||
ChannelManagerPropertyMappingService $channelManagerPropertyMappingService
|
||
)
|
||
{
|
||
|
||
//$this->tripAdvisorCookie = 'TADCID=X6AlsdAQPb0JGrWnABQCFdpBzzOuRA-9xvCxaMyI12kGsGqfJg45fcvAP84z1sQ0l6BdP8fUu-L4qe5Zh7uINoyb1usbZKS8xpY; ak_bmsc=1D49AF2E3BEE0E197E48F537CB620AA3~000000000000000000000000000000~YAAQTLOvw+g4QVN8AQAARPc0rQ3fR0x3SZUbtbLJX5hS76gF80zdg9dmKzmyonlGUQ5dyEnGt6odg1kTQUj74Xg3FeuvUX3RZZ0Oqg/HiEAMSuEbC9QgS6fugmOZLJDsJ0Vyt29BuAOYMPf/OX7lt7pCPCgfb6iQClwqh1L5ZfsjPwLrd8B9KjIKMY9hjOdpjTacei9gZoM7eR0Q3Xe2r5BoIoV4CDpM8kde/BD6rKcZbRjfNAc0KTUpHUawTS3unZ9y145u8H/99xxZCbjqrUW25g+x+hpBdI7IBr6FTXw5sljsl5V7iwfNob0NGTJF4y/37RA5KBqGatwkeMp/qdMUPBK76TyDx0q6hIwBiVxTm3y0Hq999VUmrl7Kg2IEvrACcDQNM4gKo+zjxnse+g==';
|
||
//$this->tripAdvisorCookie = 'TNI1625!AC8GutJQ28ZxO8jorf9FRDmeZ1UQdBgLItfITy8i77flGfQpI+UIoZd+cEfErXSR8VMnvDTKEW1MdkNx2GdRoJdWitJp5OC+ZjGozmfw/z8AfgvQ/IPIym57VSEZN06vcVmQulCtCPaM/q+OdmNYoEc7A3kcekslH9yl2sBLchIS';
|
||
$this->tripAdvisorCookie = '4c4b806c8afc9ae6f0d9f62e0951c53c17d4d7964446a664f74de11087849ec3';
|
||
|
||
$this->restClient = new Client([
|
||
'max' => 5,
|
||
'strict' => false,
|
||
'referer' => false,
|
||
'protocols' => ['https'],
|
||
'track_redirects' => false,
|
||
'allow_redirects' => false,
|
||
'timeout' => 5
|
||
]
|
||
);
|
||
|
||
$this->params = Input::all();
|
||
$this->currencyService = $currencyService;
|
||
$this->competitorPriceAnalysisService = $competitorPriceAnalysisService;
|
||
$this->propertyChannelMappingService = $propertyChannelMappingService;
|
||
$this->propertyRoomRatePriceService = $propertyRoomRatePriceService;
|
||
$this->propertyChannelCouponService = $propertyChannelCouponService;
|
||
$this->propertyPromotionService = $propertyPromotionService;
|
||
$this->channelManagerPropertyMappingService = $channelManagerPropertyMappingService;
|
||
$this->maxCompetitorPropertyAdd = 5;
|
||
}
|
||
|
||
public function property(Request $request)
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
try {
|
||
|
||
$this->restClientFroTripadvisr = new Client([
|
||
'max' => 5,
|
||
'verify' => false,
|
||
'http_errors' => false
|
||
]
|
||
);
|
||
|
||
$query[] = [
|
||
'query' => '84b17ed122fbdbd4',
|
||
'variables' => [
|
||
'request' => [
|
||
'query' => $this->params['keyword'],
|
||
'limit' => 10,
|
||
'scope' => 'WORLDWIDE',
|
||
'locale' => 'tr-TR',
|
||
'scopeGeoId' => 1,
|
||
'searchCenter' => null,
|
||
'types' => ['LOCATION'],
|
||
'locationTypes' => [
|
||
'ACCOMMODATION'
|
||
],
|
||
'userId' => null,
|
||
'context' => [
|
||
'listResultType' => 'HOTEL'
|
||
],
|
||
'articleCategories' => [
|
||
'default',
|
||
'love_your_local',
|
||
'insurance_lander'
|
||
],
|
||
'enabledFeatures' => [
|
||
'typeahead-q'
|
||
]
|
||
]
|
||
],
|
||
"extensions" => [
|
||
"preRegisteredQueryId" => "5ddff5cef01e3cfd"
|
||
]
|
||
];
|
||
|
||
$result = $this->restClientFroTripadvisr->post('https://www.tripadvisor.com/data/graphql/ids', [
|
||
'headers' => [
|
||
'accept-encoding' => 'gzip, deflate',
|
||
'content-type' => 'application/json',
|
||
'origin' => 'https://www.tripadvisor.com',
|
||
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
|
||
'x-requested-by' => $this->tripAdvisorCookie,
|
||
'Cookie' => 'TAUnique=%1%enc%3AB8gsSVzcYv%2Fy%2FQcUCC8NkwTAxw1PuWKMN7hDUF0uFMBsFY3v8WCw%2B6S4L8prdRoSNox8JbUSTxk%3D; TATrkConsent=eyJvdXQiOiIiLCJpbiI6IkFMTCJ9; TASSK=enc%3AAGWI%2Fjlo8Bi8BIsq9syl2j66bc48e82FdKzTyCz99EMkiPVMHbfZ6ngS%2F%2FZ%2FLHG6rhp8Sul%2BflsMdGktQml1d%2Fv7%2BwaYcOfHfgNDsKFC%2FJK4vpvZ5AbBpyN8zPlNHh5kAQ%3D%3D; _lc2_fpi=eb26cb8958b8--01kg7mzmk5gj81kjyacda52m8t; _lc2_fpi_meta=%7B%22w%22%3A1769783415397%7D; _gcl_au=1.1.656612842.1769783416; _ga=GA1.1.959970588.1769783416; PMC=V2*MS.44*MD.20260130*LD.20260130; CM=%1%mds%2C1769783421921%2C1769869821%7C; _ga_QX0Q50ZC9P=GS2.1.s1769783415$o1$g1$t1769784141$j57$l0$h0; TASID=2CE514F9A73746734B5866FC46F2C79C; TADCID=JXGuyvNjCnVXf8NBABQChrLCR-aOT_K5vlfY_RXuc4v8CctrMv3z7OyJhtqOIyA2jOwugGFgO4oUe5amkFV2bF3B1pi0-C11DE0; TASession=V2ID.2CE514F9A73746734B5866FC46F2C79C*SQ.1*LS.Hotel_Review*HS.recommended*ES.popularity*DS.5*SAS.popularity*FPS.oldFirst*FA.1*DF.0*TRA.true; PAC=AJfQGZeJqolH8Hzh3aZ4aFh_W2_NwTyw1iLgJX-tGmLLLPZwnNUwZE10W5UpsGahNGHg2Ks3ir6Z_6VMT-2nePlhd0jEjv2wl51LQbZqgAxsvoqWpjsPObiKX0VGg8bLCCUuPQCm1PWMu6iQhRW8tlJasIUq89-o5gswGEJueIGjz6bk-Ugzm1MP8oxQBgQcCaCNPMtS5PvVLfz2craf2Xs%3D; SRT=TART_SYNC; TART=%1%enc%3Aego6iAgRUncSBJJBqhWUc6MS72Gdmrlbyt6LsE1hcOb28uizb4wsXf5kB4qidaAdxJICaUS3Vk4%3D; OptanonConsent=isGpcEnabled=0&datestamp=Mon+Mar+02+2026+15%3A44%3A46+GMT%2B0300+(GMT%2B03%3A00)&version=202601.2.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=65f573db-6363-4747-9874-4ef7843acd7e&interactionCount=1&isAnonUser=1&landingPath=https%3A%2F%2Fwww.tripadvisor.de%2FHotel_Review-g293974-d295165-Reviews-Grand_Yavuz_Hotel-Istanbul.html&groups=C0001%3A1%2CC0002%3A1%2CC0003%3A1%2CC0004%3A1&prevHadToken=0; _li_dcdm_c=.tripadvisor.de; pbjs_sharedId=04024d25-0c58-462b-a8df-f66362c4dfa8; pbjs_sharedId_cst=zix7LPQsHA%3D%3D; _lr_retry_request=true; _lr_env_src_ats=false; _gcl_aw=GCL.1772455489.null; pbjs_unifiedID=%7B%22TDID_LOOKUP%22%3A%22FALSE%22%2C%22TDID_CREATED_AT%22%3A%222026-03-02T12%3A44%3A48%22%7D; pbjs_unifiedID_cst=zix7LPQsHA%3D%3D; pbjs_li_nonid=%7B%7D; pbjs_li_nonid_cst=zix7LPQsHA%3D%3D; __gads=ID=1cd3edce1a02a3a9:T=1769783416:RT=1772455490:S=ALNI_MZw9YE9xOBfuT3GptcwBZz1kk9y8A; __gpi=UID=000012ed05a9c113:T=1769783416:RT=1772455490:S=ALNI_MZjriFWmZQ4byU9h5qi7PxTySAUPA; __eoi=ID=5e090e36e225d6f3:T=1769783416:RT=1772455490:S=AA-AfjaO0smbZgTBvQkXOeDLsC_b; _lr_sampling_rate=100; datadome=jUF6V7MInCDT_e60vxsoUawgoBJ~kcHXw8wWQdbdAkpYR1DUqrYEcBuA9knMvbo2Q67tacHuwfJkQIk9aIeMFP5dfkY4ZQhZyeNyekXYunHAxxhaJ19q4AqV0_oha73O; __vt=y16Z4GVTSrBGH8EoABQCT24E-H_BQo6gx1APGQJPtzvZqhsLjxQLo2PGhRdDojXJUj-NQn9d3uXlRiKs37Gm9slng8tYN5Ykvbhr99p9t0D5jo3uHrNEzrIfiJ_F87BCZBZZH0h1u-4d1B0A0JmN31zUaQ',
|
||
],
|
||
'body' => json_encode($query),
|
||
'decode_content' => true
|
||
]
|
||
);
|
||
|
||
$result = $result->getBody()->getContents();
|
||
$result = json_decode($result, 1);
|
||
|
||
$dataResults = $result[0]['data']['Typeahead_autocomplete']['results'];
|
||
|
||
$dataHotels = [];
|
||
foreach ($dataResults as $dataResult) {
|
||
|
||
$hotelKeyRegex = '/Hotel_Review-(.*)-Reviews/m';
|
||
preg_match_all($hotelKeyRegex, $dataResult['details']['url'], $hotelKeyMatch, PREG_SET_ORDER, 0);
|
||
$hotelKey = $hotelKeyMatch[0][1];
|
||
|
||
$dataHotels[] = [
|
||
'name' => $dataResult['details']['localizedName'],
|
||
'location' => $dataResult['details']['localizedAdditionalNames']['longOnlyHierarchy'],
|
||
'key' => $hotelKey
|
||
];
|
||
}
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => $dataHotels];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
}
|
||
|
||
public function getPropertyCompetitorPrice($params = [])
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => ''];
|
||
|
||
try {
|
||
|
||
|
||
$currency = 'USD';
|
||
$currencyRequest = fillOnUndefined($params, 'currency', 'USD');
|
||
$competitorPropertyKey = $params['competitor_property_key'];
|
||
$startDate = $params['date'];//$params['startDate'];
|
||
$hotelIdExplode = explode('-d', $competitorPropertyKey, 2);
|
||
$hotelId = $hotelIdExplode[1];
|
||
|
||
|
||
$dailyPrices = [];
|
||
$dailyPricesCache = [];
|
||
|
||
$timeStartOverall = Carbon::now();
|
||
|
||
$date = Carbon::parse($startDate)->toDateString();
|
||
|
||
//$cacheKey = 'competitorPropertyHash-' . md5($competitorPropertyKey . '-' . $day . '-' . $currencyRequest);
|
||
$cacheKey = 'competitorPropertyHash-' . md5($competitorPropertyKey . '-' . $currencyRequest);
|
||
|
||
//Cache::forget($cacheKey);
|
||
|
||
if (Cache::has($cacheKey)) {
|
||
$dailyPricesCache = Cache::get($cacheKey);
|
||
}
|
||
|
||
if (isset($dailyPricesCache[$competitorPropertyKey]) && isset($dailyPricesCache[$competitorPropertyKey][$date])) {
|
||
|
||
$dailyPrices = $dailyPricesCache[$competitorPropertyKey][$date];
|
||
|
||
} else {
|
||
|
||
$lastExchangeRate = $this->currencyService->lastExchangeRate($currency, $currencyRequest);
|
||
$currencyList = $this->currencyService->getCurrencyList();
|
||
if ($currencyList['status'] != 'success') {
|
||
throw new ApiErrorException($currencyList['message']);
|
||
}
|
||
|
||
$currencyList = pickItemFromArray('code', $currencyList['data']);
|
||
if (!in_array($currencyRequest, $currencyList)) {
|
||
throw new ApiErrorException('Invalid currency code');
|
||
}
|
||
|
||
|
||
$dailyPrices['date'] = $date;
|
||
$dailyPrices['competitorPropertyKey'] = $competitorPropertyKey;
|
||
$dailyPrices['amount'] = null;
|
||
$dailyPrices['currency'] = null;
|
||
$dailyPrices['provider'] = null;
|
||
|
||
$timeStart = Carbon::now();
|
||
try {
|
||
|
||
$query = [
|
||
"detailId" => (integer)$hotelId,
|
||
"checkIn" => $date,
|
||
"checkOut" => Carbon::parse($date)->addDay()->toDateString(),
|
||
"rooms" => [
|
||
[
|
||
"adults" => 2,
|
||
"childrenAges" => []
|
||
]
|
||
]
|
||
];
|
||
|
||
$client = new \GuzzleHttp\Client([
|
||
'max' => 5,
|
||
'strict' => false,
|
||
'referer' => false,
|
||
'protocols' => ['https'],
|
||
'timeout' => 5,
|
||
'headers' => [
|
||
'X-RapidAPI-Host' => 'travel-advisor.p.rapidapi.com',
|
||
'X-RapidAPI-Key' => 'baad80dc1cmsha3959d133378f89p1c7844jsnd2bbb8c1b529',
|
||
'Accept' => 'application/json',
|
||
'Content-Type' => 'application/json',
|
||
'Cache-Control' => 'no-cache',
|
||
'Connection' => 'keep-alive',
|
||
'Accept-Encoding' => 'gzip',
|
||
//'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
||
]
|
||
]
|
||
);
|
||
|
||
$result = $client->post('https://travel-advisor.p.rapidapi.com/hotels/v2/get-offers?currency=USD', [
|
||
'body' => json_encode($query)
|
||
]);
|
||
|
||
$result = $result->getBody()->getContents();
|
||
$result = json_decode($result, 1);
|
||
|
||
//$result = json_decode('{"data":{"AppPresentation_queryHotelCommerceV2":{"__typename":"AppPresentation_HotelCommerceResponseV2","impressions":[{"__typename":"AppPresentation_ImpressionLog","data":"noClientLog"}],"sections":[{"__typename":"AppPresentation_HotelCommerceOfferList","trackingKey":"{\"ik\":\"031acedb-8ad1-4e60-83b7-f420f74270d2_0\",\"lid\":295165,\"hlk\":\"d933245d-57cc-41bb-abe7-65c56de1dbd4\",\"sn\":\"HotelCommerceDeals\",\"haik\":\"2b2dd3584c074bf1aa0fd27e0515ee34\",\"login\":false,\"plus\":false,\"hbfk\":\"a952489d18264e5dadc24707d6e912e7\"}","trackingTitle":"HotelOfferListSection_HOTEL_COMMERCE_OFFERS","stableDiffingType":"HotelOfferListSection_HOTEL_COMMERCE_OFFERS","isComplete":false,"clusterId":"HOTEL_COMMERCE_OFFERS","offersV2":[{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=BookingCom&src=32477061&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=1&matchID=1&oos=0&cnt=13&silo=10500&bucket=903023&nrank=1&crank=1&clt=M&ttype=MobileCR&tm=286615964&managed=false&capped=false&gosox=8odt39lfF_8t2Lfd3qE3zpQ0bxS-DfXQEVCGCu56GjFwnVeP40ut1Q002MbPq6-GJ6vB_j_RINgMP7TOHy-KvSCdi6d4Z8La-qnaZPCa-GEiuUaN_QJF7VccooTGz6JmhU8LApaUfO047k4rSkKAbSnSXPyL7p4B7_t3niacNpO2A0QC9DiwQuNiQC8EQLE_tDRUphklFjaKlLCkmWspYTT4-PvHv3rN5yphvdPwOS3NkNHYdexTPEJU7pHbjPOzsXI_bXJoAOHM2nApL3ce2g&priceShown=70&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=700&rq=P&rate=62.7700&fees=7.5400&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_637ab6e45797e5e16d5d775b08bcb864&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=19aaa73e178a8f07cd8f023359c51db95&ik=a952489d18264e5dadc24707d6e912e7&aok=ca2e78acd4e24a049ddd6ec13becaa79&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus0_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":"img2/branding/hotels/Booking_Com_v2_384x164_Blue.png","providerName":"Booking.com","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=HotelsCom2&src=34516980&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=2&matchID=1&oos=0&cnt=13&silo=6103&bucket=901739&nrank=2&crank=2&clt=M&ttype=MobileCR&tm=286615964&managed=false&capped=false&gosox=3hpVWWn59hLMfGrDq-0PCWG0EYNU_kg7POsPF6ThFAsDLVIH9gwfrcn3KKhOJ77Hs9AhHIzsICyJ0mI3s8HKXpv7jGezH5fXhsJqGmqdraP3tSka3ZDOkJoU7EIJ70o5dfkzgxFaWMjETOgqmxodjD1lXD1fYejoKeMUXnsa3xIWLHmXb6IRJ4xhf-0yHS8BVJTNTk4sr1oaCXYVIZq4lELuOzeOFUU6d4EYfZ4AxRhyztoG3Eb2AOJCs0ZKWt6hRj9B443Hfn2gtB2jjrSYxtg2QAMYK-T84MC4H3H0X3M&priceShown=70&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=700&rq=P&rate=62.8200&fees=7.5400&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_24b0d2a6ed870a6180e189a001b79dd5&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=12cc54312b2eb45f0ce07e66a5c1c33f6&ik=a952489d18264e5dadc24707d6e912e7&aok=c2783e8f360947db94f58e9d83893781&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus1_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"Hotels.com","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$63","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=Destinia&src=38799788&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=3&matchID=1&oos=0&cnt=13&silo=17847&bucket=941214&nrank=13&crank=13&clt=M&ttype=MobileCR&tm=286615964&managed=false&capped=false&gosox=swfzbqjFSX7gVvosEdYcyYIGyfWsITpUvJQf-vEJqDHm9NwKePRSjyIPgYDOS3Zu3lVB4dzt7cLJszgYOeRqxckOHll06lzUQrVe3cN6hK12F3yN-JD5PgYUe25vWygGqyUJllLEK0WqQnKNdJYH_ZPUgiXvFP7XXyp0h7lT9ou0NFSmGSUWNoqUsKSZaylhNPj4-8e_es3nKmG90_A5Lc2Q0dh17FM8QlTukduM87Oxcj9tcmgA4czacCkvdx7a&priceShown=63&pm=AIWE&hac=AVAILABLE&mbl=BEAT&mbldelta=0&rq=P&rate=56.4000&fees=7.5700&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_463dd3f7e09ddac3184dd5e74ad235d4&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=1b6dc44a7278cc8c32567e3a520bb93b6&ik=a952489d18264e5dadc24707d6e912e7&aok=97efe91fd2da40e78e184ad7b9c8b7e9&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$63","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus2_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"Destinia.com","strikeThroughPrice":{"__typename":"AppPresentation_LocalizedString","string":"$74","debugValueKey":null},"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=Expedia&src=32697091&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=4&matchID=1&oos=0&cnt=13&silo=4310&bucket=910482&nrank=3&crank=3&clt=M&ttype=MobileCR&tm=286615964&managed=false&capped=false&gosox=A9XDoEHOyR8iEoOswVX8y-cW5J59D7A7N0XHbUPeLv66diINev9wjgAObbDdrO6yX5d0Q1ez6ijY2HLxwIX5W9XpaS5zSbmuZK_8Gu7vLuTAD9SGrijsQAT4286JgdpRReQPdO0gi8p8HjzLpSV7beb1PKJh4qVLBTlLxU6LEb1krsYnSBNbM8o5e0dlEj_EjuMEKK-PIa-Wjo5kGgO767Q0VKYZJRY2ipSwpJlrKWE0-Pj7x796zecqYb3T8DktzZDR2HXsUzxCVO6R24zzs7FyP21yaADhzNpwKS93Hto&priceShown=70&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=700&rq=P&rate=62.8200&fees=7.5400&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_75d1f38b85875ca0bf26dcc0945b80d7&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=13cf902d80cadc2e02215c3661782bd91&ik=a952489d18264e5dadc24707d6e912e7&aok=cc924d45a89847e890676c53a4749cce&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus3_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"Expedia.com","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":null,"commerceLink":null,"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"otelz.com","strikeThroughPrice":null,"status":"IN_PROGRESS","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=TravelocityEWS&src=54221039&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=6&matchID=1&oos=0&cnt=13&silo=11456&bucket=860112&nrank=5&crank=5&clt=M&ttype=MobileCR&tm=286615964&managed=true&capped=false&gosox=MahhiGPAvYM26fgM1Fs4y0LVZilal8soQAd2MInZ40In_xelT2u3Ixjgz2DgbhROuqk_NPFo6jTPJtKMwVpV-CMX2PFFS0t0R3BGfZbK-b9Y1Jq0QYdwPNWnPIX2GiSyMqshHc50jde-NsAl4bH3ts_GhD0pIqyi4lT55BblhQcYWrVvWWB70uU03PUBEl4af9tWrpjsJHFaPzLyh2ldrULuOzeOFUU6d4EYfZ4AxRhyztoG3Eb2AOJCs0ZKWt6hRj9B443Hfn2gtB2jjrSYxtg2QAMYK-T84MC4H3H0X3M&priceShown=70&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=700&rq=P&rate=62.8200&fees=7.5400&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=16948884fd24b04e1043a4fb842aa97be&ik=a952489d18264e5dadc24707d6e912e7&aok=82abbe08c2104f3e80985e9b18d71158&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus5_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"Travelocity","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=Agoda&src=48793666&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=7&matchID=1&oos=0&cnt=13&silo=5122&bucket=895087&nrank=6&crank=6&clt=M&ttype=MobileCR&tm=286615964&managed=false&capped=false&gosox=AVPDU6RbcFVwDmbzu1YYBtZP2QUi_h128CKV_kPJuS07F_gMAdHJfUdZc8AvnTrys1pvQMRoQTuIgA976JCaSbt03vCzSlET_r8sQ_dNDrbRg9T-G1NQPHxYQ_TbM2jw-UtdbO2VUIE_TgNh3iknx_9MM4l1otBv9IETF1TlsG1h_2lMxYV4LzkEOncYyHUB0LI9QnHjhVUlnK50nbopfWyJVyDQFH6wPS4euHOGtm6Hz1FMFNk3jN9MF76vIS3C&priceShown=70&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=700&rq=P&rate=62.8200&fees=7.5300&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_6977b14032c41291e33b975570bcb286&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=1ce3884332c82f010606b95510eb54183&ik=a952489d18264e5dadc24707d6e912e7&aok=60e8134067604ba487e187f96f906c5a&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus6_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"Agoda.com","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=CtripTA&src=79214115&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=8&matchID=1&oos=0&cnt=13&silo=13669&bucket=899272&nrank=7&crank=7&clt=M&ttype=MobileCR&tm=286615964&managed=false&capped=false&gosox=lsSswv72QfbQgHuBbiLSFYE96DuL8ibgqcCcE_kHNN_Km9dkuLDm3L_m7RS8TJ3HivMyOCtiFJw9AfjfwuKpbEVsJk90QOwx9LCM4T2yPAJh7pkq6NTEKg0WDZ-hQs8lqb4uKT8BvbntHGMsxczemosM8iUgx9LuUm4EROAIHrXC9Gj0-zlbhNnfi4QogBrlrJpHfyCEDtt94CM_z-5cGGHhfsgOsRiGcJYkNnQrWhC3R27nhgsncVtnNAe50s6W&priceShown=70&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=700&rq=P&rate=62.8100&fees=7.5400&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_596afd5c88483c206cc4318df651709e&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=18f2d4afbf696d011dbe4059cc42ee964&ik=a952489d18264e5dadc24707d6e912e7&aok=12a5b5ad33374d5dbf7c1ee6c3d83798&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus7_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"Trip.com","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$74","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=Mirai&src=255621478&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=9&matchID=1&oos=0&cnt=13&silo=26106&bucket=979000&nrank=8&crank=8&clt=M&ttype=MobileCR&tm=286615964&managed=true&capped=false&gosox=sb95c4IBPXymK172SEJwTOq17gLqU_oE5TJLTjytnMaxAedRBBXUYoekoR-d9PMVNvrpLlBCaSTqtFK0EoJ2boOmn7uhCcmC2KGSJKrEybT4gR0orTS2T-AArDVYrR6dYi4oq6NjBVtyuYK5lmWSSKlSNP1ISAz8UxD3Fybyaf0M96yboyeG0iMId70TR_Fd28BM-yClSZkKZcUCt5Qj4mfpsweKUIlmCaDUaTygVHxh4X7IDrEYhnCWJDZ0K1oQt0du54YLJ3FbZzQHudLOlg&priceShown=74&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=1100&rq=P&rate=67.0700&fees=6.7100&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_1b7639ec01898e034557cc27443ed58d&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=17098edcf450ce800b98a43c64c737828&ik=a952489d18264e5dadc24707d6e912e7&aok=dddc0118d4ec4025b3bd3a8c56cd61f0&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$74","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus8_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"Official website","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$67","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=StayForLong&src=143811959&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=10&matchID=1&oos=0&cnt=13&silo=40511&bucket=944068&nrank=9&crank=9&clt=M&ttype=MobileCR&tm=286615964&managed=false&capped=false&gosox=iU3wCxpEHfKz1_yraLhvgLECmoIxDl97J66m2TgHHDHBaXRNIPUumERknooRzmLhEf1P3JKVjztLN-P06N6ZoJs7YuP0twV4ttX9T7P2xCVP6M-JfITK9FzYFiRtslp_bNXI4HjtY-enzYzJfOrNNoE1QjUcqgXJchXu2mYd5xM21Y_InGFgm4ssJnJCe_hHYf9pTMWFeC85BDp3GMh1AdCyPUJx44VVJZyudJ26KX1siVcg0BR-sD0uHrhzhrZuh89RTBTZN4zfTBe-ryEtwg&priceShown=67&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=400&rq=P&rate=66.0100&fees=0.9900&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_1a2c43030be33dc2825829366987f63a&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=18f8f91cf2b981d48d9a18cd57013d982&ik=a952489d18264e5dadc24707d6e912e7&aok=ced593da26634a0d99b705e4a8d92827&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$67","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus9_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"StayForLong","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=Vio&src=256849928&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=11&matchID=1&oos=0&cnt=13&silo=47493&bucket=997319&nrank=10&crank=10&clt=M&ttype=MobileCR&tm=286615964&managed=false&capped=false&gosox=qi8Pk1Rxoc1dwj6TlSgFnDHmTstrv7hj9b7zE8y_bU1UhxfB0tE_98RArd2AGTDUfqleSf3zWjw73eZ_KKqYWhD2WpKqmjNXuuKaHjmd90ShD8E-GDEqX0YFTdRe7DHJnw-aMYVFolfl6BPtHd2VDX3OrZqMMJuXF4BjH4EnXAghnAXWcstQ_ATM_czA23uMxrvsCe2kVfhGH30bQ8AVpnzO-4h0JzsHHOAlD7-2zqL5HNWwqmBPMFSYFlkr7Hpm9lTNXFfW_xzFOmhEb7yupJBLokGIyXFrxUiGNMxHAZU&priceShown=70&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=700&rq=P&rate=62.7800&fees=7.5300&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_1db480d711ee619f011e9cb65cc805d7&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=184590a69616cbf405d1409a6ec8a78b8&ik=a952489d18264e5dadc24707d6e912e7&aok=fa05ea300e294464b826633eae70aa87&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus10_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"Vio.com","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=BookingeDreamsWL&src=60220887&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=12&matchID=1&oos=0&cnt=13&silo=35404&bucket=914257&nrank=11&crank=11&clt=M&ttype=MobileCR&tm=286615964&managed=true&capped=false&gosox=wAYrrGjW7Bydo3kUpJXG9x-_rTT4xfM27ciwH-8l-DFHQoXiw53-HkoXH2woreUgPEQGNzWoU0UWdeapcHF6TC2ZZTDzaMEmDVNA1Ju9Ksf3PkgqLsaIz3LZAuwdMtNPvqhfyhXVqIaPY00bJD0LJE-0Cn622zDgb7Kfys-XrUCA00kLdR6Ct3D621MI2FwsqpyEGSk8F_hZm79XabgIE9lfMxamOainLytCpOUDBa13BFhBr8Q51Y0eTw8h_GtuOIcjIhb4Huqemyr-_eDENA&priceShown=70&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=700&rq=P&rate=62.7700&fees=7.5400&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=1780cec9318b617c63e6e6c6e2f041d05&ik=a952489d18264e5dadc24707d6e912e7&aok=e4d17c75ad2a42529ce22b377ebafcea&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus11_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"eDreams","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]},{"__typename":"AppPresentation_HotelCommerceOfferDeal","displayPrice":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"commerceLink":{"__typename":"AppPresentation_ExternalLink","externalUrl":"/Commerce?p=OrbitzEWS&src=78874101&geo=295165&from=HotelDateSearch_HotelCommerceDeals&slot=13&matchID=1&oos=0&cnt=13&silo=20728&bucket=862895&nrank=11&crank=12&clt=M&ttype=MobileCR&tm=286615964&managed=true&capped=false&gosox=duEJfdKRjSaUExDPyWiVTxfzxv14vo3_X57IHuv-PENZzSU0u_qCcSugyEI21SUpyTdJYppCoBfhowdBz1tytuSVcY2ctKopxjdDSR4zUA8bk_9BDHix8tffG7gOF9URKgwSVrp1OniloXJJ3ME0EJzucmES6J8quJoqj2ReWepGgoURtfMoOFl-stDivIvoQmMxR6jRfSXd2WGXNXtHBnzO-4h0JzsHHOAlD7-2zqL5HNWwqmBPMFSYFlkr7Hpm9lTNXFfW_xzFOmhEb7yupJBLokGIyXFrxUiGNMxHAZU&priceShown=70&pm=AIWE&hac=AVAILABLE&mbl=LOSE&mbldelta=700&rq=P&rate=62.8200&fees=7.5400&cur=USD&adults=2&child_rm_ages=&inDay=18&outDay=19&rdex=RDEX_445cc1f599f38d72d456f6729697e683&rooms=1&inMonth=3&inYear=2024&outMonth=3&outYear=2024&auid=a087cf4c-7eeb-4052-9492-74e1ec4dc0d4&def_d=false&bld=L_1,D_47,G_2,W_1,U_0,C_240318,T_15&bh=true&cs=10084d859975c54482a6a57bb7df14f32&ik=a952489d18264e5dadc24707d6e912e7&aok=c3ebd3b4b4534b04b485a5d5ebfa84d3&tp=APS-HotelCommerceDeals&pageLocId=295165","text":{"__typename":"AppPresentation_LocalizedString","string":"$70","debugValueKey":null},"accessibilityString":null,"trackingContext":"server_nonPlus12_hotelCommerceLink"},"details":[{"__typename":"AppPresentation_LocalizedString","string":"Free breakfast included","debugValueKey":null}],"providerLogoUrl":null,"providerName":"Orbitz.com","strikeThroughPrice":null,"status":"AVAILABLE","roomUrgencyMessage":null,"labels":[]}]},{"__typename":"AppPresentation_LogicalBreak","stableDiffingType":"LogicalBreakSection","spacing":"spacing-03","clusterId":null,"divider":null,"background":null},{"__typename":"AppPresentation_OmnibusDisclosure","link":null,"contentText":{"__typename":"AppPresentation_HtmlString","htmlString":"Prices are provided by our partners, and reflect average nightly room rates, including taxes and fees that are fixed, known to our partners, and due at time of booking. Other miscellaneous taxes and hotel fees which are not fixed or due at time of booking may be payable at the property at time of stay. Please see our partners for more details."},"trackingTitle":"HotelDealsDisclaimerSection","trackingKey":"{\"dt\":\"HOTEL_DEALS\",\"ik\":\"031acedb-8ad1-4e60-83b7-f420f74270d2_1\",\"sn\":\"HotelCommerceDeals\"}","stableDiffingType":"HotelDealsDisclaimerSection","clusterId":null},{"__typename":"AppPresentation_LogicalBreak","stableDiffingType":"LogicalBreakSection_1","spacing":"spacing-03","clusterId":null,"divider":null,"background":null}],"statusV2":{"__typename":"AppPresentation_SuccessQueryResponseStatus","partial":false,"pollingStatus":{"__typename":"AppPresentation_QueryResponsePollingStatus","delayForNextPollInMillis":10,"updateToken":"eyJ2ZXIiOiJ2MiIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2IiwidmVyc2lvbiI6IjEifQ.eyJvYmplY3QiOiJ7XCJAY1wiOlwiLlVwZGF0ZVRva2VuXCIsXCJ0eXBlXCI6XCJQT0xMSU5HXCIsXCJjbHVzdGVySWRzXCI6W1wiSE9URUxfQ09NTUVSQ0VfT0ZGRVJTXCJdLFwicHJvdmlkZXJVcGRhdGVUb2tlbnNcIjp7XCJIT1RFTF9ERVRBSUxfUFJPVklERVJcIjp7XCJAY1wiOlwiLlN0cmluZ1ZhbHVlUHJvdmlkZXJVcGRhdGVUb2tlblwiLFwidmFsdWVcIjpudWxsfX0sXCJwb2xsaW5nU2VxdWVuY2VOdW1cIjoxfSJ9.MjRlNTU4MmItMGIyNS00YTI1LTk4NDUtYTY0YjFiNzYxODc0Lk1FVUNJRVNiQlU2bWIxTjlvYV80VWx3eEwtTXY4UmZRal9fMGFvOXB1WGMtRG5rVUFpRUFsUDJPVUdMYzBHY2NpRTlXLVJOUXBtT3FLNlZKSHpfV3NmSFZmNlpjaFhV"}},"updatedClusterIds":[]}}}', 1);
|
||
|
||
if (isset($result['data']['AppPresentation_queryHotelCommerceV2']['sections'][0]['offersV2'])) {
|
||
|
||
$dataResults = $result['data']['AppPresentation_queryHotelCommerceV2']['sections'][0]['offersV2'];
|
||
|
||
$dailyPricesByProvider = [];
|
||
foreach ($dataResults as $dataResult) {
|
||
|
||
if ($dataResult['displayPrice']) {
|
||
$priceText = str_replace('$', '', $dataResult['displayPrice']['string']);
|
||
$provider = $dataResult['providerName'];
|
||
|
||
if ($priceText) {
|
||
|
||
$taxesAndFeesText = empty($taxesAndFeesText) ? 0 : $taxesAndFeesText;
|
||
$dailyPricesByProvider[] = [
|
||
'amount' => round(moneyDoubleFormatDecimal((($priceText + $taxesAndFeesText)) * $lastExchangeRate['data'])),
|
||
'currencyCode' => $currencyRequest,
|
||
'provider' => $provider,
|
||
'logo' => config('app.url') . '/img/channel-icons/' . mb_strtolower(str_replace('.', '', $provider)) . '.png',
|
||
];
|
||
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
if (!empty($dailyPricesByProvider)) {
|
||
|
||
$rateCollect = collect($dailyPricesByProvider);
|
||
$rateCollect = $rateCollect->sortBy('amount')->toArray();
|
||
$minRate = reset($rateCollect);
|
||
|
||
$dailyPrices['amount'] = $minRate['amount'];
|
||
$dailyPrices['currency'] = $currencyRequest;
|
||
$dailyPrices['provider'] = $minRate['provider'];
|
||
$dailyPrices['all'] = array_values($rateCollect);
|
||
|
||
if (Cache::has($cacheKey)) {
|
||
$dailyPricesCache = Cache::get($cacheKey);
|
||
}
|
||
|
||
$dailyPrices['cacheTime'] = Carbon::now()->toDateTimeString();
|
||
|
||
$dailyPricesCache[$dailyPrices['competitorPropertyKey']][$dailyPrices['date']] = $dailyPrices;
|
||
|
||
Cache::put($cacheKey, $dailyPricesCache, 60 * 5);//600 10 dk
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
} catch (Exception $e) {
|
||
Log::debug($e->getMessage());
|
||
//dd($e->getMessage());
|
||
}
|
||
|
||
$timeEnd = Carbon::now();
|
||
$timeDiff = Carbon::parse($timeStart)->floatDiffInRealSeconds(Carbon::parse($timeEnd));
|
||
//$dailyPrices[$hotelKey][$day]['time'] = $timeDiff;
|
||
$dailyPrices['time'] = $timeDiff;
|
||
|
||
}
|
||
|
||
$timeEndOverall = Carbon::now();
|
||
$timeDiffOverall = Carbon::parse($timeStartOverall)->floatDiffInRealSeconds(Carbon::parse($timeEndOverall));
|
||
|
||
//$dailyPrices['responseTime'] = $timeDiffOverall;
|
||
|
||
$response = ['status' => true, 'message' => null, 'data' => $dailyPrices];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
}
|
||
|
||
return $response;
|
||
|
||
}
|
||
|
||
public function propertyCompetitorPrice(Request $request)
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
try {
|
||
|
||
$params = $request->all();
|
||
|
||
|
||
if (isset($params['property_id'])) {
|
||
|
||
|
||
$channelManagerPropertyMappingCriteria = [
|
||
'criteria' =>
|
||
[
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $params['property_id']],
|
||
['field' => 'channel_manager_id', 'condition' => '=', 'value' => 12],
|
||
['field' => 'status', 'condition' => '=', 'value' => 1],
|
||
],
|
||
'firstRow' => true
|
||
];
|
||
|
||
$channelManagerPropertyMapping = $this->channelManagerPropertyMappingService->select($channelManagerPropertyMappingCriteria);
|
||
|
||
if ($channelManagerPropertyMapping['status'] == 'success' && !empty($channelManagerPropertyMapping['data'])) {
|
||
$params['competitor_property_key'] = $channelManagerPropertyMapping['data']['channel_manager_property_id'];
|
||
} else {
|
||
throw new ApiErrorException('Mapping not found.');
|
||
}
|
||
|
||
|
||
}
|
||
|
||
$paramCompetitorPrice = [
|
||
'date' => $params['date'],
|
||
'competitor_property_key' => $params['competitor_property_key'],
|
||
'currency' => $params['currency'],
|
||
];
|
||
|
||
$propertyCompetitorPrice = $this->getPropertyCompetitorPrice($paramCompetitorPrice);
|
||
|
||
if (!$propertyCompetitorPrice['status']) {
|
||
throw new ApiErrorException($propertyCompetitorPrice['message']);
|
||
}
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => $propertyCompetitorPrice['data']];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
}
|
||
|
||
public function createPropertyCompetitor(Request $request)
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
try {
|
||
|
||
$requestSelectCriteria = [
|
||
'criteria' => [
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $this->params['params']['property_id']],
|
||
]
|
||
];
|
||
|
||
|
||
$requestSelectResult = $this->competitorPriceAnalysisService->selectPropertyCompetitorMapping($requestSelectCriteria);
|
||
|
||
if ($requestSelectResult['status'] != 'success') {
|
||
throw new ApiErrorException($requestSelectResult['message']);
|
||
}
|
||
|
||
if (count($requestSelectResult['data']) >= $this->maxCompetitorPropertyAdd) {
|
||
//throw new ApiErrorException('Up to 5 records can be added.');
|
||
}
|
||
|
||
|
||
$requestParam = [
|
||
'property_id' => $this->params['params']['property_id'],
|
||
'competitor_property_name' => $this->params['params']['competitor_property_name'],
|
||
'competitor_property_key' => $this->params['params']['competitor_property_key'],
|
||
'created_by' => $request->auth->id,
|
||
'updated_by' => $request->auth->id,
|
||
];
|
||
|
||
$requestResult = $this->competitorPriceAnalysisService->createPropertyCompetitorMapping($requestParam);
|
||
|
||
if ($requestResult['status'] != 'success') {
|
||
throw new ApiErrorException($requestResult['message']);
|
||
}
|
||
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => $requestResult['data']];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
}
|
||
|
||
public function syncPropertyCompetitor(Request $request)
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
try {
|
||
|
||
$requestSelectCriteria = [
|
||
'criteria' => [
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $this->params['params']['property_id']],
|
||
]
|
||
];
|
||
|
||
|
||
$requestSelectResult = $this->competitorPriceAnalysisService->selectPropertyCompetitorMapping($requestSelectCriteria);
|
||
|
||
if ($requestSelectResult['status'] != 'success') {
|
||
throw new ApiErrorException($requestSelectResult['message']);
|
||
}
|
||
|
||
DB::beginTransaction();
|
||
|
||
if (!empty($requestSelectResult['data'])) {
|
||
$deleteIds = pickItemFromArray('id', $requestSelectResult['data']);
|
||
$deletePropertyCompetitorMapping = $this->competitorPriceAnalysisService->deletePropertyCompetitorMapping($deleteIds);
|
||
if ($deletePropertyCompetitorMapping['status'] != 'success') {
|
||
throw new ApiErrorException($deletePropertyCompetitorMapping['message']);
|
||
}
|
||
}
|
||
|
||
|
||
$competitorProperty = $this->params['params']['competitor'];
|
||
|
||
|
||
if (count($competitorProperty) > $this->maxCompetitorPropertyAdd) {
|
||
//throw new ApiErrorException('Up to 5 records can be added.');
|
||
}
|
||
|
||
$requestResultData = [];
|
||
foreach ($competitorProperty as $property) {
|
||
|
||
$requestParam = [
|
||
'property_id' => $this->params['params']['property_id'],
|
||
'competitor_property_name' => $property['name'],
|
||
"competitor_property_source" => fillOnUndefined($property, "competitor_property_source"),
|
||
'competitor_property_key' => $property['key'],
|
||
'created_by' => $request->auth->id,
|
||
'updated_by' => $request->auth->id,
|
||
];
|
||
|
||
$requestResult = $this->competitorPriceAnalysisService->createPropertyCompetitorMapping($requestParam);
|
||
|
||
if ($requestResult['status'] != 'success') {
|
||
throw new ApiErrorException($requestResult['message']);
|
||
}
|
||
|
||
$requestResultData[] = $requestResult['data'];
|
||
|
||
}
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => $requestResultData];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
|
||
if ($response['status']) {
|
||
DB::commit();
|
||
} else {
|
||
DB::rollBack();
|
||
}
|
||
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
}
|
||
|
||
public function getPropertyCompetitor(Request $request)
|
||
{
|
||
|
||
$params = $request->all();
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
try {
|
||
|
||
$requestSelectCriteria = [
|
||
'criteria' => [
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $params['params']['property_id']],
|
||
]
|
||
];
|
||
|
||
$columns = ['id', 'property_id', 'competitor_property_name', 'competitor_property_key','competitor_property_source'];
|
||
$requestSelectResult = $this->competitorPriceAnalysisService->selectPropertyCompetitorMapping($requestSelectCriteria, $columns);
|
||
|
||
if ($requestSelectResult['status'] != 'success') {
|
||
throw new ApiErrorException($requestSelectResult['message']);
|
||
}
|
||
|
||
if (empty($requestSelectResult['data'])) {
|
||
throw new ApiErrorException(lang('Mapping data not found'));
|
||
}
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => $requestSelectResult['data']];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
}
|
||
|
||
public function deletePropertyCompetitor(Request $request)
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
try {
|
||
|
||
$requestSelectCriteria = [
|
||
'criteria' => [
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $this->params['params']['property_id']],
|
||
['field' => 'id', 'condition' => '=', 'value' => $this->params['params']['property_competitor_mapping_id']]
|
||
], 'firstRow' => true
|
||
];
|
||
|
||
|
||
$requestSelectResult = $this->competitorPriceAnalysisService->selectPropertyCompetitorMapping($requestSelectCriteria);
|
||
|
||
if ($requestSelectResult['status'] != 'success') {
|
||
throw new ApiErrorException($requestSelectResult['message']);
|
||
}
|
||
|
||
if (empty($requestSelectResult['data'])) {
|
||
throw new ApiErrorException(lang('Mapping data not found'));
|
||
}
|
||
|
||
$deletePropertyCompetitorMapping = $this->competitorPriceAnalysisService->deletePropertyCompetitorMapping($requestSelectResult['data']['id']);
|
||
|
||
if ($deletePropertyCompetitorMapping['status'] != 'success') {
|
||
throw new ApiErrorException($deletePropertyCompetitorMapping['message']);
|
||
}
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => []];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
}
|
||
|
||
public function propertyCompetitorExcel(Request $request)
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
try {
|
||
|
||
$params = $this->params['params'];
|
||
|
||
if (Carbon::parse($params['start_date'])->isBefore(Carbon::now()) && !Carbon::parse($params['start_date'])->isToday()) {
|
||
throw new ApiErrorException('Comparison possible for today and next days.');
|
||
}
|
||
|
||
if (Carbon::parse($params['start_date'])->diffInDays(Carbon::parse($params['finish_date'])) > 14) {
|
||
throw new ApiErrorException('A maximum of 14 days of data can be retrieved.');
|
||
}
|
||
|
||
$requestSelectCriteria = [
|
||
'criteria' => [
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $params['property_id']],
|
||
]
|
||
];
|
||
|
||
$columns = ['id', 'property_id', 'competitor_property_name', 'competitor_property_key','competitor_property_source'];
|
||
$propertyCompetitor = $this->competitorPriceAnalysisService->selectPropertyCompetitorMapping($requestSelectCriteria, $columns);
|
||
|
||
if ($propertyCompetitor['status'] != 'success') {
|
||
throw new ApiErrorException($propertyCompetitor['message']);
|
||
}
|
||
|
||
if (empty($propertyCompetitor['data'])) {
|
||
throw new ApiErrorException(lang('Mapping data not found'));
|
||
}
|
||
|
||
$dataTableData = [];
|
||
|
||
$dateFirst = $params['start_date'];
|
||
$dateDiff = Carbon::parse($params['start_date'])->diffInDays(Carbon::parse($params['finish_date']));
|
||
foreach ($propertyCompetitor['data'] as $property) {
|
||
for ($i = 0; $i <= $dateDiff; $i++) {
|
||
|
||
$date = Carbon::parse($dateFirst)->addDays($i)->toDateString();
|
||
|
||
|
||
$propertyCompetitorPrice = [
|
||
'status' => false,
|
||
'data' => []
|
||
];
|
||
|
||
$cacheKey = 'competitorPropertyHash-' . md5($property['key'] . '-' . $params['currency']);
|
||
|
||
if (Cache::has($cacheKey)) {
|
||
$dailyPricesCache = Cache::get($cacheKey);
|
||
|
||
if (isset($dailyPricesCache[$property['key']][$date])) {
|
||
$propertyCompetitorPrice = [
|
||
'status' => true,
|
||
'data' => $dailyPricesCache[$property['key']][$date]
|
||
];
|
||
}
|
||
}
|
||
|
||
/*if(empty($propertyCompetitorPrice)) {
|
||
$paramCompetitorPrice = [
|
||
'date' => $date,
|
||
'competitor_property_key' => $property['key'],
|
||
'currency' => $params['currency'],
|
||
];
|
||
|
||
$propertyCompetitorPrice = $this->getPropertyCompetitorPrice($paramCompetitorPrice);
|
||
}*/
|
||
|
||
$dailyAmount = null;
|
||
if ($propertyCompetitorPrice['status']) {
|
||
$dailyAmount = $propertyCompetitorPrice['data']['amount'];
|
||
}
|
||
|
||
$dataTableData['value'][$property['key']][$date] = [
|
||
'amount' => $dailyAmount,
|
||
'currency' => $params['currency']
|
||
];
|
||
|
||
$dataTableData['property'][$property['key']] = $property['name'];
|
||
$dataTableData['date'][$date] = Carbon::parse($date)->format('d.m.Y');
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
$fileName = 'PropertyCompetitorExport-' . md5(implode('-', $params)) . '.xlsx';
|
||
$fileNamePath = 'excel/' . $fileName;
|
||
$fileNamePublic = config('app.url') . '/' . $fileNamePath;
|
||
|
||
$excelStore = Excel::store(new PropertyCompetitorExport($dataTableData), $fileNamePath, 'public');
|
||
|
||
if (!$excelStore) {
|
||
throw new ApiErrorException(lang('Mapping data not found'));
|
||
}
|
||
|
||
$data = [
|
||
'url' => $fileNamePublic
|
||
];
|
||
|
||
//Delete files older than 1 hours
|
||
$excelFileDir = public_path('excel');
|
||
foreach (glob($excelFileDir . '/' . "*") as $file) {
|
||
if (filemtime($file) < time() - 3600) { // 6 hours 21600
|
||
unlink($file);
|
||
}
|
||
}
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => $data];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
}
|
||
|
||
|
||
public function bestAvailablePrice(Request $request)
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
try {
|
||
|
||
$params = json_decode($request->getContent(), 1);
|
||
$params = $this->params['params'];
|
||
|
||
$params['type'] = fillOnUndefined($params, 'type', 'range');
|
||
$bookingEnginePropertyId = $params['property_id'];
|
||
|
||
$cacheKeyParam[] = $params['type'];
|
||
$cacheKeyParam[] = $bookingEnginePropertyId;
|
||
if ($params['type'] == 'range') {
|
||
|
||
if (Carbon::parse($params['start_date'])->isBefore(Carbon::now()) && !Carbon::parse($params['start_date'])->isToday()) {
|
||
throw new ApiErrorException('Comparison possible for today and next days.');
|
||
}
|
||
|
||
if (Carbon::parse($params['start_date'])->diffInDays(Carbon::parse($params['finish_date'])) > 14) {
|
||
throw new ApiErrorException('A maximum of 14 days of data can be retrieved.');
|
||
}
|
||
|
||
$cacheKeyParam[] = $params['start_date'] . '|' . $params['finish_date'];
|
||
} else if ($params['type'] == 'date') {
|
||
$cacheKeyParam[] = $params['date'] . '|' . $params['currency'];
|
||
}
|
||
|
||
$cacheKey = 'CRR:' . md5(implode(',', $cacheKeyParam));
|
||
|
||
|
||
//Cache::forget($cacheKey);
|
||
if (Cache::has($cacheKey)) {
|
||
$responseData = Cache::get($cacheKey);
|
||
} else {
|
||
|
||
$requestParam = [
|
||
'criteria' => [
|
||
['field' => 'channel_id', 'condition' => '=', 'value' => 5],
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $bookingEnginePropertyId],
|
||
['field' => 'status', 'condition' => '=', 'value' => 1],
|
||
],
|
||
'with' => ['currency'],
|
||
'firstRow' => true
|
||
];
|
||
|
||
$getChannelProperty = $this->propertyChannelMappingService->select($requestParam);
|
||
$getChannelProperty = $getChannelProperty['status'] == 'success' && !empty($getChannelProperty['data']) ? $getChannelProperty['data'] : null;
|
||
|
||
if (!$getChannelProperty) {
|
||
throw new ApiErrorException('Property Channel not found');
|
||
}
|
||
|
||
|
||
switch ($params['type']) {
|
||
|
||
case 'range' :
|
||
|
||
$requestParam = [
|
||
'criteria' => [
|
||
['field' => 'channel_id', 'condition' => '=', 'value' => 5],
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $bookingEnginePropertyId],
|
||
['field' => 'stop_sell', 'condition' => '=', 'value' => 0],
|
||
['field' => 'date', 'condition' => '>=', 'value' => \Carbon\Carbon::parse($params['start_date'])->format('Y-m-d')],
|
||
['field' => 'date', 'condition' => '<', 'value' => Carbon::parse($params['finish_date'])->addDay()->format('Y-m-d')],
|
||
['field' => 'amount', 'condition' => '<>', 'value' => null],
|
||
['field' => 'amount', 'condition' => '<>', 'value' => 0],
|
||
['field' => 'status', 'condition' => '=', 'value' => 1],
|
||
],
|
||
'with' => [
|
||
'roomRateMapping.propertyRoomRate'
|
||
],
|
||
'orderBy' => [
|
||
['field' => 'date', 'value' => 'ASC'],
|
||
['field' => 'amount', 'value' => 'ASC']
|
||
],
|
||
];
|
||
|
||
$getPropertyRoomRatePrice = $this->propertyRoomRatePriceService->select($requestParam);
|
||
|
||
if ($getPropertyRoomRatePrice['status'] != 'success' || empty($getPropertyRoomRatePrice['data'])) {
|
||
throw new ApiErrorException(lang('PropertyRoomRatePrice not found'));
|
||
}
|
||
|
||
$getPropertyRoomRatePrice = $getPropertyRoomRatePrice['status'] == 'success' && !empty($getPropertyRoomRatePrice['data']) ? $getPropertyRoomRatePrice['data'] : [];
|
||
|
||
$firstDate = Carbon::parse($params['start_date'])->format('Y-m-d');
|
||
$lastDate = Carbon::parse($params['finish_date'])->addDay()->format('Y-m-d');
|
||
$dateDiff = Carbon::parse($firstDate)->diffInDays(Carbon::parse($lastDate));
|
||
|
||
$rateAndAvailability = [];
|
||
for ($i = 0; $i < $dateDiff; $i++) {
|
||
$date = Carbon::parse($firstDate)->addDays($i)->toDateString();
|
||
|
||
$bestPrice = collect($getPropertyRoomRatePrice)
|
||
->where('date', $date)
|
||
->where('room_rate_mapping.property_room_rate.name', 'Best Available Rate')
|
||
->sortBy('amount')->first();
|
||
$bestPrice = $bestPrice ? $bestPrice['amount'] : null;
|
||
|
||
$rateAndAvailability[$date] = [
|
||
'rate' => round($bestPrice),
|
||
'available' => $bestPrice ? true : false,
|
||
];
|
||
|
||
}
|
||
|
||
$responseData = [
|
||
'currency' => $getChannelProperty['currency']['code'],
|
||
'currency_icon' => $getChannelProperty['currency']['symbol'],
|
||
'date' => $rateAndAvailability
|
||
];
|
||
|
||
|
||
if (count($rateAndAvailability) == collect($rateAndAvailability)->where('available', false)->count()) {
|
||
$responseData = null;
|
||
}
|
||
|
||
Cache::put($cacheKey, $responseData, 60 * 60);//1h 60 * 60
|
||
|
||
break;
|
||
|
||
case 'date' :
|
||
|
||
$requestParam = [
|
||
'criteria' => [
|
||
['field' => 'channel_id', 'condition' => '=', 'value' => 5],
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $bookingEnginePropertyId],
|
||
['field' => 'stop_sell', 'condition' => '=', 'value' => 0],
|
||
['field' => 'date', 'condition' => '=', 'value' => Carbon::parse($params['date'])->format('Y-m-d')],
|
||
['field' => 'amount', 'condition' => '<>', 'value' => null],
|
||
['field' => 'amount', 'condition' => '<>', 'value' => 0],
|
||
['field' => 'status', 'condition' => '=', 'value' => 1],
|
||
],
|
||
'with' => [
|
||
'roomRateMapping.propertyRoomRate'
|
||
],
|
||
'orderBy' => [
|
||
['field' => 'date', 'value' => 'ASC'],
|
||
['field' => 'amount', 'value' => 'ASC']
|
||
],
|
||
];
|
||
|
||
$getPropertyRoomRatePrice = $this->propertyRoomRatePriceService->select($requestParam);
|
||
|
||
if ($getPropertyRoomRatePrice['status'] != 'success' || empty($getPropertyRoomRatePrice['data'])) {
|
||
throw new ApiErrorException(lang('PropertyRoomRatePrice not found'));
|
||
}
|
||
|
||
$getPropertyRoomRatePrice = $getPropertyRoomRatePrice['status'] == 'success' && !empty($getPropertyRoomRatePrice['data']) ? $getPropertyRoomRatePrice['data'] : [];
|
||
|
||
|
||
$rateAndAvailability = [];
|
||
$bestPriceSelect = collect($getPropertyRoomRatePrice)
|
||
->where('date', $params['date'])
|
||
->where('room_rate_mapping.property_room_rate.name', 'Best Available Rate')
|
||
->sortBy('amount')->first();
|
||
|
||
$bestPrice = $bestPriceSelect ? $bestPriceSelect['amount'] : null;
|
||
|
||
if ($params['currency'] != $bestPriceSelect['currency']) {
|
||
$lastExchangeRate = $this->currencyService->lastExchangeRate($bestPriceSelect['currency'], $params['currency']);
|
||
$bestPrice = $bestPrice * $lastExchangeRate['data'];
|
||
|
||
}
|
||
|
||
$rateAndAvailability[$params['date']] = [
|
||
'rate' => round($bestPrice),
|
||
'available' => $bestPrice ? true : false,
|
||
];
|
||
|
||
$responseData = [
|
||
'currency' => $params['currency'],
|
||
'date' => $rateAndAvailability
|
||
];
|
||
|
||
if (count($rateAndAvailability) == collect($rateAndAvailability)->where('available', false)->count()) {
|
||
$responseData = null;
|
||
}
|
||
|
||
Cache::put($cacheKey, $responseData, 60 * 60);//1h 60 * 60
|
||
|
||
break;
|
||
|
||
}
|
||
|
||
|
||
}
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => $responseData];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch
|
||
(Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
|
||
}
|
||
|
||
|
||
public function propertyCompetitorAnalysis(Request $request)
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
try {
|
||
|
||
$data = null;
|
||
$params = $this->params['params'];
|
||
|
||
$openAiToken = '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';
|
||
|
||
$client = new \GuzzleHttp\Client([
|
||
'max' => 5,
|
||
'strict' => false,
|
||
'referer' => false,
|
||
'protocols' => ['https'],
|
||
'timeout' => 30,
|
||
'headers' => [
|
||
'Authorization' => 'Bearer ' . $openAiToken,
|
||
'Content-Type' => 'application/json',
|
||
'Cache-Control' => 'no-cache',
|
||
'Connection' => 'keep-alive',
|
||
'Accept-Encoding' => 'gzip'
|
||
]
|
||
]
|
||
);
|
||
|
||
|
||
$query = [
|
||
'model' => 'gpt-4o',
|
||
'response_format' => [
|
||
'type' => 'json_object'
|
||
],
|
||
'messages' => [
|
||
[
|
||
'role' => 'system',
|
||
'content' => 'Sen bir otel gelir yönetimi (revenue management) uzmanısın. Görev: 1. Verilen fiyatları analiz et. 2. İstenen dilde yorum ve öneri üret. Cevap alanları: {"property": "string", "currency": "string", "average_price": "float", "average_difference": "float (10,2)", "price_position": "string", "competitor_average_price": "float (10,2)", "highest_difference_day": "string (format: d.m.Y)", "highest_difference_percent": "float", "language": "string", "comment": "string", "recommendation": "string"}. price_position değeri higher, lower veya similar olabilir. highest_difference_day ve highest_difference_percent rakiplere göre en yüksek kalan güne göre, format: float(10.2). price_position, average_difference ve competitor_average_price, rakiplerin ortalamalarına göre, format: float. Sadece geçerli JSON döndür, başka açıklama veya metin ekleme.'
|
||
],
|
||
[
|
||
'role' => 'user',
|
||
'content' => json_encode($params)
|
||
]
|
||
]
|
||
|
||
];
|
||
|
||
$result = $client->post('https://api.openai.com/v1/chat/completions', [
|
||
'body' => json_encode($query)
|
||
]);
|
||
|
||
$result = $result->getBody()->getContents();
|
||
$result = json_decode($result, 1);
|
||
|
||
$choiceMessage = reset($result['choices']);
|
||
|
||
if (isset($choiceMessage['message']['content'])) {
|
||
$choiceMessage = json_decode($choiceMessage['message']['content'], 1);
|
||
$data = $choiceMessage;
|
||
}
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => $data];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
}
|
||
|
||
public function promotionAvailable(Request $request)
|
||
{
|
||
|
||
$response = ['status' => false, 'message' => '', 'data' => null, 'statusCode' => 500];
|
||
|
||
$promotionAvailableData = [];
|
||
|
||
try {
|
||
|
||
$data = null;
|
||
$params = $this->params['params'];
|
||
|
||
|
||
//property_channel_coupon
|
||
$propertyChannelCouponData = null;
|
||
$propertyChannelCouponCriteria = [
|
||
'criteria' => [
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $params['property_id']],
|
||
['field' => 'channel_id', 'condition' => '=', 'value' => 1],
|
||
['field' => 'start_date', 'condition' => '<=', 'value' => Carbon::parse($params['date'])->toDateString()],
|
||
['field' => 'end_date', 'condition' => '>=', 'value' => Carbon::parse($params['date'])->toDateString()],
|
||
['field' => 'is_notify', 'condition' => '=', 'value' => 1],
|
||
['field' => 'status', 'condition' => '=', 'value' => 1],
|
||
],
|
||
'orderBy' => [
|
||
['field' => 'value', 'value' => 'DESC']
|
||
],
|
||
];
|
||
|
||
$propertyChannelCoupon = $this->propertyChannelCouponService->select($propertyChannelCouponCriteria);
|
||
|
||
if ($propertyChannelCoupon['status'] == 'success') {
|
||
foreach ($propertyChannelCoupon['data'] as $coupon) {
|
||
if (!empty($coupon['reservation_start_date']) && !empty($coupon['reservation_end_date'])) {
|
||
if (Carbon::parse($params['date'])->betweenIncluded(Carbon::parse($coupon['reservation_start_date']), Carbon::parse($coupon['reservation_end_date']))) {
|
||
$propertyChannelCouponData = [
|
||
'type' => $coupon['type'],
|
||
'value' => $coupon['value'],
|
||
];
|
||
break;
|
||
} else {
|
||
continue;
|
||
}
|
||
} else {
|
||
$propertyChannelCouponData = [
|
||
'type' => $coupon['type'],
|
||
'value' => $coupon['value'],
|
||
];
|
||
}
|
||
}
|
||
}
|
||
$promotionAvailableData['popup'] = $propertyChannelCouponData;
|
||
//property_channel_coupon
|
||
|
||
|
||
//property_promotion
|
||
|
||
|
||
//property_promotion
|
||
$propertyPromotionData = null;
|
||
$promotionTypes = ['PRD', 'BFD', 'LST', 'DSC'];
|
||
$propertyPromotionCriteria = [
|
||
'criteria' => [
|
||
['field' => 'property_id', 'condition' => '=', 'value' => $params['property_id']],
|
||
['field' => 'status', 'condition' => '=', 'value' => 1],
|
||
],
|
||
'with' => ['promotionType'],
|
||
'orderBy' => [
|
||
['field' => 'promotion_type_id', 'value' => 'ASC']
|
||
],
|
||
];
|
||
|
||
$propertyPromotions = $this->propertyPromotionService->select($propertyPromotionCriteria);
|
||
|
||
$propertyPromotionGroup = null;
|
||
if ($propertyPromotions['status'] == 'success') {
|
||
|
||
foreach ($propertyPromotions['data'] as $propertyPromotion) {
|
||
$propertyPromotionGroup[$propertyPromotion['promotion_type']['type_code']][$propertyPromotion['id']] = $propertyPromotion;
|
||
}
|
||
|
||
|
||
foreach ($promotionTypes as $promotionType) {
|
||
|
||
$propertyPromotionData[$promotionType] = null;
|
||
if (isset($propertyPromotionGroup[$promotionType])) {
|
||
|
||
switch ($promotionType) {
|
||
|
||
case 'PRD':
|
||
|
||
$propertyPromotionSelected = collect($propertyPromotionGroup[$promotionType])
|
||
->where('start_date', '<=', Carbon::parse($params['date'])->toDateString())
|
||
->where('end_date', '>=', Carbon::parse($params['date'])->toDateString())
|
||
->where('reservation_start_date', '<=', Carbon::parse($params['date'])->toDateString())
|
||
->where('reservation_end_date', '>=', Carbon::parse($params['date'])->toDateString())
|
||
->sortByDesc('amount')
|
||
->first();
|
||
|
||
if ($propertyPromotionSelected) {
|
||
|
||
$propertyPromotionData[$promotionType] = [
|
||
'type' => 'PER',
|
||
'value' => $propertyPromotionSelected['amount'],
|
||
'is_mobile' => $propertyPromotionSelected['is_mobile'],
|
||
];
|
||
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
case 'BFD' || 'LST':
|
||
|
||
|
||
$propertyPromotionSelected = collect($propertyPromotionGroup[$promotionType])
|
||
->sortByDesc('amount')
|
||
->toArray();
|
||
|
||
if ($propertyPromotionSelected) {
|
||
|
||
foreach ($propertyPromotionSelected as $perPromotion) {
|
||
|
||
if (Carbon::parse($params['date'])->subDays($perPromotion['day_before'])->lessThanOrEqualTo(Carbon::now())) {
|
||
$propertyPromotionData[$promotionType] = [
|
||
'type' => 'PER',
|
||
'value' => $perPromotion['amount'],
|
||
'is_mobile' => $perPromotion['is_mobile'],
|
||
];
|
||
break;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
break;
|
||
case 'DSC':
|
||
|
||
|
||
$propertyPromotionSelected = collect($propertyPromotionGroup[$promotionType])
|
||
->sortByDesc('amount')
|
||
->first();
|
||
|
||
if ($propertyPromotionSelected) {
|
||
$propertyPromotionData[$promotionType] = [
|
||
'type' => 'PER',
|
||
'value' => $propertyPromotionSelected['amount'],
|
||
'is_mobile' => $propertyPromotionSelected['is_mobile'],
|
||
];
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//dd($propertyPromotionGroup);
|
||
|
||
$promotionAvailableData['promotion'] = $propertyPromotionData;
|
||
|
||
|
||
}
|
||
|
||
|
||
$response = ['status' => 1, 'statusCode' => 200, 'message' => null, 'data' => $promotionAvailableData];
|
||
|
||
} catch (ApiErrorException $e) {
|
||
$response['message'] = implode(', ', $e->getMessageArr());
|
||
$response['statusCode'] = 400;
|
||
} catch (Exception $e) {
|
||
$message = $e->getFile() . " " . $e->getLine() . " " . $e->getMessage();
|
||
Log::error($message);
|
||
$response['message'] = $e->getMessage();
|
||
$response['statusCode'] = 500;
|
||
}
|
||
|
||
return apiResponse($response['status'], $response['message'], $response['data'], $response['statusCode']);
|
||
|
||
}
|
||
|
||
|
||
}
|
||
|