Files
api-extranetwork/app/Http/Controllers/propertyInsertController.php
ExtraNetwork e5c4b6aa13 first commit
2026-05-12 17:04:54 +03:00

1787 lines
88 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use App\Core\Service\PropertyService;
use App\Core\Service\PropertyContactService;
use App\Core\Service\PropertyFactService;
use App\Core\Service\PropertyFactMappingService;
use App\Core\Service\PropertyRoomService;
use App\Core\Service\UserService;
use App\Core\Service\UserPropertyMappingService;
use App\Core\Service\JwtService;
use App\Core\Service\ApiAccessTokenService;
use App\Core\Service\ProductService;
use App\Exceptions\ApiErrorException;
use App\Models\PropertyFact;
use App\Models\PropertyPhoto;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Intervention\Image\Facades\Image;
/**
* propertyInsertController
*
* Booking.com'dan gelen otel verisiyle property oluşturma akışını
* tek endpoint üzerinden uçtan uca çalıştırır.
*
* AKIŞ:
* ADIM 1 — Kullanıcı kaydı + property oluşturma (user/register-user-with-property)
* ADIM 2 — Property bilgilerini güncelle (property/info/update)
* ADIM 3 — İletişim bilgilerini kaydet (property/contact/update)
* ADIM 4 — Facility eşleştir → fact-mapping ekle (property/fact-mapping/add)
* ADIM 5 — Oda ekle (property/room/add-room-bed)
*
* KULLANIM:
* POST /bulut/property-insert
* {
* "hotel_id" : "89675", // Booking.com hotel_id (zorunlu)
* "email" : "owner@hotel.com", // kullanıcı e-posta (zorunlu)
* "password" : "Abc123!", // kullanıcı şifre (zorunlu)
* "name" : "Ahmet", // kullanıcı adı (zorunlu)
* "surname" : "Yılmaz", // kullanıcı soyadı (zorunlu)
* "property_name" : "Grand Yavuz", // property adı (zorunlu)
* "phone" : "+90...", // iletişim telefonu (opsiyonel)
* "dry_run" : false // true → DB'ye yazma, sadece rapor üret
* }
*/
class propertyInsertController extends Controller
{
const API_HOST = 'booking-com15.p.rapidapi.com';
const API_BASE = 'https://booking-com15.p.rapidapi.com';
// ─────────────────────────────────────────────────────────────────────────────
// Alias map: Booking.com facility adı → property_fact.name (küçük harf)
// ─────────────────────────────────────────────────────────────────────────────
private $aliasMap = array(
'internet services' => 'wi-fi',
'free wifi' => 'wi-fi',
'wifi' => 'wi-fi',
'wi-fi' => 'wi-fi',
'wifi in all areas' => 'wi-fi',
'free wi-fi' => 'wi-fi',
'internet' => 'wi-fi',
'internet access' => 'wi-fi',
'airport shuttle' => 'airport transfer',
'airport pick up' => 'airport to hotel transfer',
'airport drop off' => 'hotel to airport transfer',
'car hire' => 'rent a car',
'car rental' => 'rent a car',
'shuttle service' => 'airport transfer',
'24-hour front desk' => 'reception',
'concierge service' => 'concierge',
'luggage storage' => 'luggage storage',
'laundry' => 'cleaning service',
'daily housekeeping' => 'housekeeping',
'tour desk' => 'tour desk',
'private check-in/check-out' => 'check-in service',
'express check-in/check-out' => 'check-in service',
'wake-up service' => 'wake up service',
'dry cleaning' => 'dry cleaning',
'currency exchange' => 'currency exchange',
'facilities for disabled guests' => 'disabled',
'wheelchair accessible' => 'wheelchair',
'upper floors accessible by elevator' => 'elevator',
'lower bathroom sink' => 'lower bathroom sink',
'toilet with grab rails' => 'toilet with grab rails',
'breakfast' => 'bed and breakfast',
'snack bar' => 'snack bar',
'meeting/banquet facilities' => 'meeting room',
'business centre' => 'working area',
'business center' => 'working area',
'fax/photocopying' => 'photocopy',
'air conditioning' => 'air conditioner',
'flat-screen tv' => 'tv',
'flat screen tv' => 'tv',
'bath or shower' => 'shower',
'free toiletries' => 'shower',
'single-room air conditioning for guest accommodation' => 'air conditioner',
'single-room ac for guest accommodation' => 'air conditioner',
'minibar' => 'mini bar',
'hairdryer' => 'hair dryer',
'hair dryer' => 'hair dryer',
'wardrobe or closet' => 'clothes cabinet',
'socket near the bed' => 'planning electrical sockets',
'family rooms' => 'family room',
'clothes rack' => 'clotheshorse',
'cctv in common areas' => 'security camera',
'cctv outside property' => 'security camera',
'smoke alarms' => 'smoke alarm',
'safety deposit box' => 'safety deposit box',
'24-hour security' => 'security service',
'key card access' => 'security service',
'hand sanitizer in guest accommodation and key areas' => 'hand sanitiser',
'hand sanitiser' => 'hand sanitiser',
'hand sanitizer' => 'hand sanitiser',
'face masks for guests available' => 'protective masks for guests',
'physical distancing rules followed' => 'social distancing regulations',
'screens or physical barriers placed between staff and guests in appropriate areas' => 'protective hygiene screens',
'guest accommodation is disinfected between stays' => 'disinfection in all rooms',
'cashless payment available' => 'contactless payment',
'food can be delivered to guest accommodation' => 'food delivery',
'tennis court' => 'tennis',
'golf course' => 'golf',
'table tennis' => 'ping pong',
'jogging track' => 'jogging',
'lift' => 'elevator',
'elevator' => 'elevator',
);
// ─────────────────────────────────────────────────────────────────────────────
// Oda olanaklarına özel alias: Booking.com oda facility adı → property_fact.name
// ─────────────────────────────────────────────────────────────────────────────
private $roomAliasMap = array(
'safe' => 'safe box',
'towels' => 'towel',
'linens' => 'towel',
'tea/coffee maker' => 'coffee maker',
'coffee/tea maker' => 'coffee maker',
'electric kettle' => 'kettle',
'private bathroom' => 'bathroom',
'telephone' => 'telephone',
'toilet paper' => 'toilet',
'free toiletries' => 'hair dryer',
'slippers' => 'bathrobe',
'bath or shower' => 'shower',
'bathtub or shower' => 'shower',
'shower only' => 'shower',
);
// ─────────────────────────────────────────────────────────────────────────────
// GuzzleHttp → Booking.com API
// ─────────────────────────────────────────────────────────────────────────────
private function bookingClient()
{
return new Client(array(
'timeout' => 30,
'headers' => array(
'x-rapidapi-host' => env('BULUT_RAPIDAPI_HOST', self::API_HOST),
'x-rapidapi-key' => env('BULUT_RAPIDAPI_KEY'),
'Content-Type' => 'application/json',
),
));
}
// ─────────────────────────────────────────────────────────────────────────────
// ADIM 0: Booking.com'dan otel verilerini çek
// ─────────────────────────────────────────────────────────────────────────────
private function fetchBookingData($hotelId, $checkin, $checkout)
{
$client = $this->bookingClient();
// Otel detayı (property bilgileri: isim, konum, dil, timezone vb.)
$detailResp = $client->get(self::API_BASE . '/api/v1/hotels/getHotelDetails', array(
'query' => array(
'hotel_id' => $hotelId,
'arrival_date' => $checkin,
'departure_date' => $checkout,
'adults' => 1,
'room_qty' => 1,
'languagecode' => 'en-us',
'currency_code' => 'USD',
'units' => 'metric',
'temperature_unit' => 'c',
),
));
$detail = json_decode($detailResp->getBody()->getContents(), true);
$detail = isset($detail['data']) ? $detail['data'] : array();
// Oda listesi — tüm oda tiplerini çek (getRoomList daha kapsamlı)
// getHotelDetails sadece o güne müsait odaları döndürür, getRoomList tüm tipleri verir
$checkinRooms = date('Y-m-d', strtotime('+30 days'));
$checkoutRooms = date('Y-m-d', strtotime('+37 days'));
$roomListResp = $client->get(self::API_BASE . '/api/v1/hotels/getRoomList', array(
'query' => array(
'hotel_id' => $hotelId,
'arrival_date' => $checkinRooms,
'departure_date' => $checkoutRooms,
'adults' => 2,
'room_qty' => 1,
'currency_code' => 'EUR',
'languagecode' => 'en-us',
),
));
$roomListData = json_decode($roomListResp->getBody()->getContents(), true);
$roomListData = isset($roomListData['data']) ? $roomListData['data'] : array();
// getRoomList'ten gelen block[] ve rooms[] ile detail'i zenginleştir
if (!empty($roomListData['block'])) {
$detail['block'] = $roomListData['block'];
}
if (!empty($roomListData['rooms'])) {
$detail['rooms'] = $roomListData['rooms'];
}
// Facility listesi
$facilResp = $client->get(self::API_BASE . '/api/v1/hotels/getHotelFacilities', array(
'query' => array('hotel_id' => $hotelId),
));
$facilData = json_decode($facilResp->getBody()->getContents(), true);
$facilData = isset($facilData['data']) ? $facilData['data'] : array();
// Otel fotograflari
$photoResp = $client->get(self::API_BASE . '/api/v1/hotels/getHotelPhotos', array(
'query' => array('hotel_id' => $hotelId),
));
$photoData = json_decode($photoResp->getBody()->getContents(), true);
return array('detail' => $detail, 'facilities' => $facilData, 'photos' => $photoData);
}
// ─────────────────────────────────────────────────────────────────────────────
// ADIM 1: Kullanıcı + Property oluştur
// → user/register-user-with-property mantığı
// ─────────────────────────────────────────────────────────────────────────────
private function createUserAndProperty(array $params, $propertyName, $userId = null)
{
// Kullanıcı zaten varsa sadece property ekle
$userService = app(\App\Core\Service\UserService::class);
$propertyService = app(\App\Core\Service\PropertyService::class);
$userPropertyMappingService = app(\App\Core\Service\UserPropertyMappingService::class);
$productService = app(\App\Core\Service\ProductService::class);
$jwtService = app(\App\Core\Service\JwtService::class);
$apiAccessTokenService = app(\App\Core\Service\ApiAccessTokenService::class);
// Kullanıcı oluştur
$userParams = array(
'name' => $params['name'],
'surname' => $params['surname'],
'email' => $params['email'],
'password' => $params['password'],
'status' => 1,
'user_type' => 1,
'property_name' => $propertyName,
);
$userCreate = $userService->create($userParams);
if ($userCreate['status'] != 'success') {
throw new ApiErrorException('Kullanıcı oluşturulamadı: ' . $userCreate['message']);
}
$user = $userCreate['data'];
// UserService::create rastgele şifre üretiyor (Str::random(6))
// Kullanıcının istediği şifreyi DB'ye doğrudan güncelle
DB::table('user')
->where('id', $user['id'])
->update(array('password' => \Illuminate\Support\Facades\Hash::make($params['password'])));
// Property oluştur
$propertyCreate = $propertyService->create(array(
'name' => $propertyName,
'status' => 1,
'created_by' => $user['id'],
'updated_by' => $user['id'],
'created_at' => time(),
'updated_at' => time(),
));
if ($propertyCreate['status'] != 'success') {
throw new ApiErrorException('Property oluşturulamadı: ' . $propertyCreate['message']);
}
$property = $propertyCreate['data'];
// User-Property mapping
$mappingCreate = $userPropertyMappingService->create(array(
'user_id' => $user['id'],
'property_id' => $property['id'],
'status' => 1,
'created_by' => $user['id'],
'updated_by' => $user['id'],
'created_at' => time(),
'updated_at' => time(),
));
if ($mappingCreate['status'] != 'success') {
throw new ApiErrorException('Kullanıcı-Property eşleştirme hatası: ' . $mappingCreate['message']);
}
// Default ürünleri ata
$productService->setDefaultPropertyProducts(array(
'user_id' => $user['id'],
'property_id' => $property['id'],
));
// JWT token al
$jwtResult = $jwtService->jwtCreate(array('user_id' => $user['id']));
if ($jwtResult['status'] != 'success') {
throw new ApiErrorException('Token oluşturulamadı');
}
$jwt = $jwtResult['data'];
$apiAccessTokenService->create(array(
'token' => md5(fillOnUndefined($jwt, 'token')),
'expire_date' => fillOnUndefined($jwt, 'exp'),
'user_id' => $user['id'],
'invalidate' => fillOnUndefined($jwt, 'invalidate', 0),
));
return array(
'user' => $user,
'property_id' => $property['id'],
'token' => $jwt['token'],
);
}
// ─────────────────────────────────────────────────────────────────────────────
// ADIM 2: Property bilgilerini güncelle
// → property/info/update
// Booking.com detail → property_info, additional_info, locale
// ─────────────────────────────────────────────────────────────────────────────
private function updatePropertyInfo($propertyId, $userId, array $detail)
{
$propertyService = app(\App\Core\Service\PropertyService::class);
$name = isset($detail['hotel_name']) ? $detail['hotel_name'] : null;
$raw = isset($detail['rawData']) ? $detail['rawData'] : array();
// Stars: rawData.accuratePropertyClass > rawData.propertyClass > rawData.qualityClass
$rating = null;
foreach (array('accuratePropertyClass', 'propertyClass', 'qualityClass') as $rk) {
if (!empty($raw[$rk])) {
$rating = (int) $raw[$rk];
break;
}
}
// Check-in/out: rawData.checkin.fromTime / rawData.checkout.untilTime
$checkIn = isset($raw['checkin']['fromTime']) ? $raw['checkin']['fromTime'] : null;
$checkOut = isset($raw['checkout']['untilTime']) ? $raw['checkout']['untilTime'] : null;
// Ülke kodu (max 5 char)
$countryCode = null;
foreach (array('countryCode', 'countrycode', 'country_code') as $ck) {
if (!empty($raw[$ck])) {
$countryCode = strtoupper($raw[$ck]);
break;
}
}
if (!$countryCode && !empty($detail['countrycode'])) {
$countryCode = strtoupper($detail['countrycode']);
}
// Konuşulan diller: "en-gb" → "en_gb", "tr" → "tr"
$spokenRaw = isset($detail['spoken_languages']) ? $detail['spoken_languages'] : array('en');
$langCodes = array();
foreach ($spokenRaw as $lang) {
$normalized = str_replace('-', '_', strtolower($lang));
// Sadece 2 veya 5 harfli geçerli kodlar
if (preg_match('/^[a-z]{2}(_[a-z]{2})?$/', $normalized)) {
$langCodes[] = $normalized;
}
}
if (empty($langCodes)) {
$langCodes = array('en');
}
$updateParams = array(
'property_id' => $propertyId,
'user_id' => $userId,
'locale' => 'en',
'has_locale_name' => false,
'property_language_spoken' => $langCodes,
'property_info' => array(
'name' => $name,
'property_type_id' => 1, // 1 = Hotel
'chain_id' => 1, // 1 = Independent
'rating' => $rating,
'country' => $countryCode,
),
'additional_info' => array(
'checkin_time' => $checkIn,
'checkout_time' => $checkOut,
'number_of_rooms' => !empty($detail['number_of_rooms']) ? (string)$detail['number_of_rooms'] : null,
'property_timezone' => !empty($detail['timezone'])
? $this->resolveTimezoneId($detail['timezone'])
: null,
),
);
$result = $propertyService->propertyUpdate($updateParams);
if ($result['status'] != 'success') {
throw new ApiErrorException('Property güncelleme hatası: ' . $result['message']);
}
return $result['data'];
}
// ─────────────────────────────────────────────────────────────────────────────
// ADIM 3: İletişim bilgilerini kaydet
// → property/contact/update
// ─────────────────────────────────────────────────────────────────────────────
private function updatePropertyContact($propertyId, $userId, array $detail, $phone = null)
{
$propertyContactService = app(\App\Core\Service\PropertyContactService::class);
// Booking.com top-level alanlar
$email = isset($detail['email']) ? $detail['email'] : null;
$web = isset($detail['url']) ? $detail['url'] : null;
$address = isset($detail['address']) ? $detail['address'] : null;
$zip = isset($detail['zip']) ? $detail['zip'] : null;
$lat = isset($detail['latitude']) ? $detail['latitude'] : null;
$lng = isset($detail['longitude']) ? $detail['longitude'] : null;
if (!$phone && isset($detail['phone'])) {
$phone = $detail['phone'];
}
// Validator: email required|email — boşsa placeholder kullan
if (!$email) {
$email = 'property' . $propertyId . '@extranetwork.com';
}
// Validator: phone veya mobile gerekli
if (!$phone) {
$phone = '0000000000';
}
$contactParams = array(
'property_id' => $propertyId,
'user_id' => $userId,
'contact' => array(
'phone' => $phone,
'email' => $email,
'web' => $web,
'address' => $address,
'zip_code' => $zip,
'latitude' => $lat,
'longitude' => $lng,
),
);
$result = $propertyContactService->propertyContactUpdateOrCreate($contactParams);
if ($result['status'] != 'success') {
throw new ApiErrorException('İletişim kaydetme hatası: ' . $result['message']);
}
return $result['data'];
}
// ─────────────────────────────────────────────────────────────────────────────
// ADIM 4: Facility eşleştir → property_fact_mapping'e ekle
// → property/fact-mapping/add
// ─────────────────────────────────────────────────────────────────────────────
private function mapAndSaveFacilities($propertyId, $userId, array $facilData)
{
$propertyFactMappingService = app(\App\Core\Service\PropertyFactMappingService::class);
// Tüm API isimlerini topla
$apiNames = array();
$addUniq = function ($name) use (&$apiNames) {
$name = trim($name);
if ($name !== '' && !in_array($name, $apiNames)) {
$apiNames[] = $name;
}
};
if (isset($facilData['facilities']) && is_array($facilData['facilities'])) {
foreach ($facilData['facilities'] as $fac) {
if (!empty($fac['instances'])) {
foreach ($fac['instances'] as $inst) {
if (!empty($inst['title'])) {
$addUniq($inst['title']);
}
}
}
}
}
if (isset($facilData['accommodationHighlights']) && is_array($facilData['accommodationHighlights'])) {
foreach ($facilData['accommodationHighlights'] as $h) {
if (!empty($h['title'])) {
$addUniq($h['title']);
}
}
}
if (isset($facilData['highlights']) && is_array($facilData['highlights'])) {
foreach ($facilData['highlights'] as $h) {
if (!empty($h['instances'])) {
foreach ($h['instances'] as $inst) {
if (!empty($inst['title'])) {
$addUniq($inst['title']);
}
}
}
}
}
// DB exact index
$allFacts = PropertyFact::select(array('id', 'name', 'type'))->get();
$exactIndex = array();
foreach ($allFacts as $fact) {
$key = mb_strtolower(trim($fact->name));
if (!isset($exactIndex[$key]) || $fact->type == 1) {
$exactIndex[$key] = $fact;
}
}
$saved = array();
$skipped = array();
foreach ($apiNames as $apiName) {
$lower = mb_strtolower(trim($apiName));
$searchKey = isset($this->aliasMap[$lower]) ? $this->aliasMap[$lower] : $lower;
$fact = null;
$matchType = 'none';
// 1. Exact / alias
if (isset($exactIndex[$searchKey])) {
$fact = $exactIndex[$searchKey];
$matchType = ($searchKey !== $lower) ? 'alias' : 'exact';
}
// 2. DB LIKE
if (!$fact) {
$like = PropertyFact::where('type', 1)
->whereRaw('LOWER(name) LIKE ?', array('%' . $lower . '%'))
->orderByRaw('LENGTH(name) ASC')
->first();
if ($like) {
$fact = $like;
$matchType = 'like';
}
}
// 3. Ters LIKE
if (!$fact) {
$reverse = PropertyFact::where('type', 1)
->whereRaw('LOWER(?) LIKE CONCAT(\'%\', LOWER(name), \'%\')', array($lower))
->whereRaw('LENGTH(name) >= 5')
->orderByRaw('LENGTH(name) DESC')
->first();
if ($reverse) {
$fact = $reverse;
$matchType = 'reverse_like';
}
}
if ($fact) {
// Kaydet
$mappingResult = $propertyFactMappingService->addPropertyFactMapping(array(
'property_id' => $propertyId,
'user_id' => $userId,
'data' => array(
array('id' => $fact->id),
),
));
$saved[] = array(
'api_name' => $apiName,
'fact_id' => $fact->id,
'fact_name' => $fact->name,
'match_type' => $matchType,
'saved' => ($mappingResult['status'] == 'success'),
);
} else {
$skipped[] = $apiName;
}
}
return array(
'api_total' => count($apiNames),
'saved_count' => count($saved),
'skipped_count' => count($skipped),
'saved' => $saved,
'skipped' => $skipped,
);
}
// ─────────────────────────────────────────────────────────────────────────────
// ADIM 5: Booking.com'daki oda bilgilerinden oda ekle
// → property/room/add-room-bed
// ─────────────────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// Booking.com bed_type ID → sistemdeki property_room_bed_type.id
//
// Booking.com standart IDs (DataCrawler API):
// 1=Twin 2=Double 3=King 4=Queen 5=Single 6=Sofa 7=Bunk 8=Futon
// Sistemdeki property_room_bed_type:
// 1=Bunk Bed 2=Double Bed 3=French Bed 4=Futon 5=King Bed
// 6=Queen Bed 7=Single Bed 8=Sofa Bed 9=Twin Bed 10=Foldable Bed
// 11=Sleeping Bag 12=Triple Bunk Bed 13=Extra bed 14=Cot
// ─────────────────────────────────────────────────────────────────────────────
private $bedTypeMap = array(
1 => 9, // Twin Bed(s) → Twin Bed
2 => 2, // Double Bed → Double Bed
3 => 5, // King Bed → King Bed
4 => 6, // Queen Bed → Queen Bed
5 => 7, // Single Bed → Single Bed
6 => 8, // Sofa Bed → Sofa Bed
7 => 1, // Bunk Bed → Bunk Bed
8 => 4, // Futon → Futon
9 => 3, // French Bed → French Bed
10 => 10, // Foldable Bed → Foldable Bed
37 => 14, // Cot/Cribs → Cot
40 => 13, // Extra Bed → Extra bed
);
// ─────────────────────────────────────────────────────────────────────────────
// Booking.com view adı (highlight.translated_name) → property_room_view_type.id
// ─────────────────────────────────────────────────────────────────────────────
private $viewTypeMap = array(
'park view' => 1,
'pool view' => 2,
'river view' => 3,
'sea view' => 4,
'ocean view' => 22,
'street view' => 5,
'valley view' => 6,
'monument view' => 7,
'bay view' => 8,
'beach view' => 9,
'city view' => 10,
'countryside view' => 11,
'courtyard view' => 12,
'garden view' => 13,
'gulf view' => 14,
'harbor view' => 15,
'lagoon view' => 16,
'lake view' => 17,
'marina view' => 18,
'mountain view' => 19,
'nature view' => 20,
'no window' => 21,
'land view' => 23,
'forest view' => 24,
'island view' => 25,
'golf course view' => 26,
'landmark view' => 30,
'sea view' => 4,
);
// ─────────────────────────────────────────────────────────────────────────────
// Booking.com oda adı keyword → property_room_type.id
// ─────────────────────────────────────────────────────────────────────────────
private $roomTypeKeywords = array(
'standard' => 24, // Standard Room
'standart' => 24,
'single' => 42, // Single Room
'double' => 43, // Double Room
'twin' => 43, // Double Room (çoğu twin=double planı)
'triple' => 44, // Triple Room
'quad' => 45, // Quadruple Room
'family' => 18, // Family Room
'suite' => 25, // Suite
'junior suite' => 20,
'grand suite' => 19,
'king suite' => 21,
'deluxe' => 16, // Deluxe
'superior' => 26, // Superior
'executive' => 48, // Executive Room
'villa' => 27, // Villa
'bungalow' => 14, // Bungalow
'loft' => 31, // Loft Room
'studio' => 29, // Studio Room
'apart' => 12, // Apartment
'apartment' => 12,
'penthouse' => 22, // Pent House
'accessible' => 11, // Accessible Room
'honeymoon' => 34, // Honeymoon Room
'economy' => 17, // Economy
'premium' => 23, // Premium Room
'club' => 15, // Club Room
'swim up' => 46, // Swim Up Room
'swim-up' => 46,
'corner' => 49, // Corner Room
'large' => 47, // Standard Large Room
);
// ─────────────────────────────────────────────────────────────────────────────
// ADIM 5: Booking.com'daki oda bilgilerinden oda ekle
//
// Kaynak 1: detail['block'][] → room_name, max_occupancy, nr_adults,
// room_surface_in_m2, number_of_bathrooms
// Kaynak 2: detail['rooms'][id] → bed_configurations, highlights (view)
// ─────────────────────────────────────────────────────────────────────────────
private function insertRooms($propertyId, $userId, array $detail)
{
$propertyRoomService = app(\App\Core\Service\PropertyRoomService::class);
// ── Kaynak 1: block[] → her room_id için tek kayıt ──────────────────────
$blockByRoomId = array();
$blockRaw = isset($detail['block']) ? $detail['block'] : array();
foreach ($blockRaw as $b) {
$rid = isset($b['room_id']) ? $b['room_id'] : null;
if ($rid && !isset($blockByRoomId[$rid])) {
$blockByRoomId[$rid] = $b;
}
}
// ── Kaynak 2: rooms[room_id] → bed configs + view highlights ────────────
$roomsRaw = isset($detail['rooms']) ? $detail['rooms'] : array();
// rooms[] boşsa sadece block verisini kullan
if (empty($roomsRaw) && empty($blockByRoomId)) {
// Hiç oda yoksa tek generic oda
$blockByRoomId = array(
0 => array(
'room_name' => 'Standard Room',
'max_occupancy' => 2,
'nr_adults' => 2,
'room_surface_in_m2' => null,
'number_of_bathrooms' => null,
),
);
}
// rooms[] boşsa block ID'lerini rooms olarak kullan
if (empty($roomsRaw)) {
foreach ($blockByRoomId as $rid => $b) {
$roomsRaw[$rid] = array();
}
}
if (empty($blockByRoomId)) {
foreach (array_keys($roomsRaw) as $rid) {
$blockByRoomId[$rid] = array();
}
}
// Distinct room_id birleşimi
$allRoomIds = array_unique(array_merge(array_keys($blockByRoomId), array_keys($roomsRaw)));
$inserted = array();
foreach ($allRoomIds as $roomId) {
$block = isset($blockByRoomId[$roomId]) ? $blockByRoomId[$roomId] : array();
$room = isset($roomsRaw[$roomId]) ? $roomsRaw[$roomId] : array();
// ── Oda adı ──────────────────────────────────────────────────────────
$roomName = trim(isset($block['room_name']) ? $block['room_name'] : 'Standard Room');
// ── room_type_id: oda adındaki anahtar kelimeye göre ─────────────────
$roomTypeId = $this->guessRoomTypeId($roomName);
// ── Kapasite ─────────────────────────────────────────────────────────
$maxOccupancy = isset($block['max_occupancy']) ? (int)$block['max_occupancy'] : 2;
$maxAdult = isset($block['nr_adults']) ? (int)$block['nr_adults'] : $maxOccupancy;
$maxChild = isset($block['nr_children']) ? (int)$block['nr_children'] : 0;
// ── Oda boyutu ───────────────────────────────────────────────────────
// Validator required|max:30 → null yerine 0 gönder
$roomSizeRaw = isset($block['room_surface_in_m2']) ? (int)$block['room_surface_in_m2'] : 0;
$roomSize = $roomSizeRaw > 0 ? $roomSizeRaw : 0;
$roomSizeType = $roomSizeRaw > 0 ? 1 : 0; // 1 = m²
// ── Oda adedi (kaç adet bu tipten var) ──────────────────────────────
$roomTypeCount = isset($block['room_count']) && $block['room_count'] > 0
? (int)$block['room_count'] : 1;
// ── Banyo sayısı ─────────────────────────────────────────────────────
$bathroomCount = isset($block['number_of_bathrooms']) && $block['number_of_bathrooms'] > 0
? (int)$block['number_of_bathrooms'] : null;
// ── Açıklama ─────────────────────────────────────────────────────────
$description = isset($room['description']) ? $room['description'] : null;
// ── Yatak konfigürasyonları → room_bed_group ─────────────────────────
// Sistem formatı: [[{bed_type_id:9}, {bed_type_id:9}], [{bed_type_id:2}]]
// Her conf = bir grup, içindeki bed_types[].count kadar tekrar
$bedConfigs = isset($room['bed_configurations']) ? $room['bed_configurations'] : array();
$roomBedGroup = array();
foreach ($bedConfigs as $conf) {
$groupBeds = array();
$bedTypes = isset($conf['bed_types']) ? $conf['bed_types'] : array();
foreach ($bedTypes as $bt) {
$bookingBedId = isset($bt['bed_type']) ? (int)$bt['bed_type'] : 0;
$ourBedTypeId = isset($this->bedTypeMap[$bookingBedId]) ? $this->bedTypeMap[$bookingBedId] : null;
$bedCount = isset($bt['count']) ? (int)$bt['count'] : 1;
if ($ourBedTypeId) {
for ($i = 0; $i < $bedCount; $i++) {
$groupBeds[] = array('bed_type_id' => $ourBedTypeId);
}
}
}
if (!empty($groupBeds)) {
$roomBedGroup[] = $groupBeds;
}
}
// ── View tipleri → room_view_type ────────────────────────────────────
// Booking.com highlights'daki "translated_name" → viewTypeMap eşleşmesi
// Eşleşmezse icon'a bak: "city"→10, "sea"→4, "pool"→2, "mountain"→19
$iconViewMap = array(
'city' => 10,
'sea' => 4,
'ocean' => 22,
'pool' => 2,
'mountain' => 19,
'garden' => 13,
'park' => 1,
'river' => 3,
'lake' => 17,
'beach' => 9,
'forest' => 24,
'harbor' => 15,
'marina' => 18,
'eye' => 4, // Booking.com Sea view ikonu "eye" olarak geliyor
'landmark' => 30, // Landmark view
);
$highlights = isset($room['highlights']) ? $room['highlights'] : array();
$roomViewType = array();
foreach ($highlights as $h) {
$hName = mb_strtolower(trim(isset($h['translated_name']) ? $h['translated_name'] : ''));
$hIcon = mb_strtolower(trim(isset($h['icon']) ? $h['icon'] : ''));
$viewId = null;
// 1) Tam eşleşme: "city view"
if (isset($this->viewTypeMap[$hName])) {
$viewId = $this->viewTypeMap[$hName];
}
// 2) Kısmi eşleşme: highlight adında keyword geçiyor
if (!$viewId) {
foreach ($this->viewTypeMap as $keyword => $vid) {
$base = str_replace(' view', '', $keyword);
if (strpos($hName, $base) !== false) {
$viewId = $vid;
break;
}
}
}
// 3) Icon eşleşmesi: "city" ikonu → 10
if (!$viewId && isset($iconViewMap[$hIcon])) {
$viewId = $iconViewMap[$hIcon];
}
if ($viewId && !in_array($viewId, $roomViewType)) {
$roomViewType[] = $viewId;
}
}
// ── Service'e gönder ─────────────────────────────────────────────────
$roomParams = array(
'property_id' => $propertyId,
'user_id' => $userId,
'locale' => 'en',
'name' => $roomName,
'room_type_id' => $roomTypeId,
'max_occupancy' => $maxOccupancy,
'max_adult' => $maxAdult,
'max_child' => $maxChild,
'occupancy_lock' => 0,
'exclude_occupancy' => 0,
'room_size' => $roomSize,
'room_size_type' => $roomSizeType,
'room_type_count' => $roomTypeCount,
'bathroom_count' => $bathroomCount,
'toilet_count' => null,
'lounge_count' => null,
'room_count' => null,
'max_child_number' => null,
'description' => array(
array('language_code' => 'en', 'description' => $description)
),
'room_bed_group' => $roomBedGroup,
'room_view_type' => $roomViewType,
'is_connected_room' => null,
'is_connected_room_price' => null,
'is_connected_room_availability' => null,
'connected_rooms' => array(),
);
$result = $propertyRoomService->addPropertyRoomAndBed($roomParams);
$isSaved = ($result['status'] == 'success' || $result['status'] === true);
$facSummary = array();
$photoSummary = array();
if ($isSaved) {
// addPropertyRoomAndBed data döndürmüyor — son eklenen room_id'yi çek
$newRoom = DB::table('property_room')
->where('property_id', $propertyId)
->where('name', $roomName)
->orderBy('id', 'desc')
->first();
$newRoomId = $newRoom ? $newRoom->id : null;
if ($newRoomId) {
// Oda olanakları → property_room_fact_mapping
if (!empty($room['facilities'])) {
$facSummary = $this->mapAndSaveRoomFacilities(
$newRoomId,
$propertyId,
$userId,
$room['facilities']
);
}
// Oda fotoğrafları → property_photo + property_room_photo_mapping
if (!empty($room['photos'])) {
$photoSummary = $this->mapAndSaveRoomPhotos(
$newRoomId,
$propertyId,
$userId,
$room['photos'],
isset($detail['hotel_id']) ? $detail['hotel_id'] : 0
);
}
}
}
$inserted[] = array(
'room_id' => $roomId,
'room_name' => $roomName,
'room_type_id' => $roomTypeId,
'beds' => array_map(function ($g) {
return $g;
}, $roomBedGroup),
'views' => $roomViewType,
'room_facts' => $facSummary,
'room_photos' => $photoSummary,
'saved' => $isSaved,
'message' => !$isSaved ? $result['message'] : null,
);
}
return array(
'room_count' => count($inserted),
'rooms' => $inserted,
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Oda fotoğraflarını indir, property_photo'ya kaydet, room_photo_mapping'e bağla
// Booking.com: rooms[id]['photos'] → [{url_original, url_max750, ...}]
// ─────────────────────────────────────────────────────────────────────────────
private function mapAndSaveRoomPhotos($roomId, $propertyId, $userId, array $photos, $hotelId)
{
if (empty($photos)) {
return array('saved_count' => 0, 'skipped_count' => 0);
}
$uploadRoot = rtrim(Config::get('app.fileSystemDriver'), '/');
$urlPath = '/property-photos/' . $propertyId . '/';
$filePath = $uploadRoot . $urlPath;
if (!File::exists($filePath)) {
File::makeDirectory($filePath, 0777, true);
}
$saved = array();
$skipped = array();
$index = 0;
foreach ($photos as $photo) {
// URL: url_original > url_max750 > url_max1280 > url_max300
$url = '';
foreach (array('url_original', 'url_max750', 'url_max1280', 'url_max300') as $key) {
if (!empty($photo[$key])) {
$url = $photo[$key];
break;
}
}
if ($url === '') {
$skipped[] = 'empty_url';
continue;
}
try {
$photoId = isset($photo['photo_id']) ? $photo['photo_id'] : '';
$fileName = time() . '_bk' . $hotelId . '_r' . $roomId . '_' . $index;
$fileExt = 'jpg';
$tempPath = sys_get_temp_dir() . '/' . $fileName . '_orig.jpg';
$this->downloadRawImage($url, $tempPath);
$image = Image::make($tempPath);
$imageWidth = $image->width();
$imageHeight = $image->height();
$fileSize = (int) ceil(filesize($tempPath) / 1024);
$quality = 80;
// Orijinal — 2000px üzeriyse küçült
if ($imageHeight > 2000 || $imageWidth > 2000) {
if ($imageHeight > $imageWidth) {
$r = $imageHeight / 2000;
$image->fit((int)($imageWidth / $r), 2000);
} else {
$r = $imageWidth / 2000;
$image->fit(2000, (int)($imageHeight / $r));
}
}
$image->encode($fileExt, $quality)->orientate()->save($filePath . $fileName . '.' . $fileExt);
// _medium (max 1600x768)
$mediumImage = Image::make($tempPath);
$ratio = $imageHeight > 0 ? ($imageHeight / 768) : 1;
$resizeWidth = (int)($imageWidth / max($ratio, 1));
if ($resizeWidth > 1599) {
$mediumImage->fit(1600, 768);
} else {
$mediumImage->fit(max($resizeWidth, 1), 768);
}
$mediumImage->encode($fileExt, $quality)->orientate()->save($filePath . $fileName . '_medium.' . $fileExt);
// _thumbnail (300x300)
Image::make($tempPath)
->fit(300, 300)
->encode($fileExt, $quality)
->orientate()
->save($filePath . $fileName . '_thumbnail.' . $fileExt);
if (File::exists($tempPath)) {
File::delete($tempPath);
}
$isCompatible = ($imageWidth >= 1150 && $imageHeight >= 600) ? 1 : 0;
$photoRecord = PropertyPhoto::create(array(
'property_id' => $propertyId,
'property_photo_category_id' => 1,
'photo_path' => $urlPath,
'photo_name' => $fileName,
'file_size' => $fileSize,
'file_ext' => $fileExt,
'photo_resolution' => $imageWidth . 'x' . $imageHeight,
'is_default' => 0,
'is_compatible_with_myweb_slider' => $isCompatible,
'photo_order' => 0,
'photo_rank' => 0,
'is_temp' => 1,
'status' => 1,
'created_by' => 1,
'updated_by' => 1,
'created_at' => time(),
'updated_at' => time(),
));
// property_room_photo_mapping'e bağla
$alreadyLinked = DB::table('property_room_photo_mapping')
->where('room_id', $roomId)
->where('photo_id', $photoRecord->id)
->exists();
if (!$alreadyLinked) {
DB::table('property_room_photo_mapping')->insert(array(
'property_id' => $propertyId,
'room_id' => $roomId,
'photo_id' => $photoRecord->id,
'created_by' => $userId,
'updated_by' => $userId,
'created_at' => time(),
'updated_at' => time(),
));
}
$saved[] = array(
'photo_id' => $photoRecord->id,
'file_name' => $fileName . '.' . $fileExt,
'resolution' => $imageWidth . 'x' . $imageHeight,
'source_url' => $url,
);
} catch (Exception $e) {
Log::error('propertyInsert.roomPhoto [room=' . $roomId . ', i=' . $index . ']: ' . $e->getMessage());
$skipped[] = $url;
}
$index++;
}
return array(
'saved_count' => count($saved),
'skipped_count' => count($skipped),
'photos' => $saved,
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Oda olanaklarını (room facilities) property_room_fact_mapping'e kaydet
// Booking.com: rooms[id]['facilities'] → [{id, name, alt_facilitytype_id, ...}]
// View tiplerini atla (viewTypeMap ile zaten ekleniyor)
// ─────────────────────────────────────────────────────────────────────────────
private function mapAndSaveRoomFacilities($roomId, $propertyId, $userId, array $facilities)
{
// View kategorisi facilitytype_id'leri (9=View, 14=View alt) — zaten view mapping'de ekleniyor
$skipFacilityTypeIds = array(9, 14);
// Sistemdeki tüm fact'leri küçük harfli isimle index'e al (type=1: property/room fact)
$allFacts = PropertyFact::select(array('id', 'name', 'type'))->where('type', 1)->get();
$exactIndex = array();
foreach ($allFacts as $fact) {
$key = mb_strtolower(trim($fact->name));
// Aynı isimde birden fazla fact varsa daha küçük id'liyi (ilk ekleneni) tut
if (!isset($exactIndex[$key])) {
$exactIndex[$key] = $fact;
}
}
// roomAliasMap (oda özel) + aliasMap (genel) birleştir, room öncelikli
$combinedAlias = array_merge($this->aliasMap, $this->roomAliasMap);
$saved = array();
$skipped = array();
foreach ($facilities as $fac) {
// View tiplerini atla
$faciltypeId = isset($fac['facilitytype_id']) ? (int)$fac['facilitytype_id'] : 0;
$altFaciltypeId = isset($fac['alt_facilitytype_id']) ? (int)$fac['alt_facilitytype_id'] : 0;
if (in_array($faciltypeId, $skipFacilityTypeIds) || in_array($altFaciltypeId, $skipFacilityTypeIds)) {
continue;
}
$name = trim(isset($fac['name']) ? $fac['name'] : '');
if ($name === '') {
continue;
}
$lower = mb_strtolower($name);
// Alias map'te varsa o karşılığı kullan, yoksa direkt ismi kullan
$searchKey = isset($combinedAlias[$lower]) ? mb_strtolower($combinedAlias[$lower]) : $lower;
$fact = null;
// Sadece birebir eşleşme — tahmin yok, LIKE yok
if (isset($exactIndex[$searchKey])) {
$fact = $exactIndex[$searchKey];
}
if ($fact) {
// Yalnızca mevcut property_room_fact_mapping tablosuna yaz, yeni kayıt türü oluşturma
$exists = DB::table('property_room_fact_mapping')
->where('property_id', $propertyId)
->where('room_id', $roomId)
->where('fact_id', $fact->id)
->exists();
if (!$exists) {
DB::table('property_room_fact_mapping')->insert(array(
'property_id' => $propertyId,
'room_id' => $roomId,
'fact_id' => $fact->id,
'is_feature' => 0,
'created_by' => $userId,
'updated_by' => $userId,
'created_at' => time(),
'updated_at' => time(),
));
}
$saved[] = array(
'booking_name' => $name,
'fact_id' => $fact->id,
'fact_name' => $fact->name,
'via_alias' => ($searchKey !== $lower),
);
} else {
// Eşleşme yok — sisteme kesinlikle bir şey yazma, sadece logla
Log::warning('propertyInsert.roomFacility.noMatch', array(
'room_id' => $roomId,
'property_id' => $propertyId,
'booking_name' => $name,
'search_key' => $searchKey,
'hint' => 'roomAliasMap veya aliasMap\'e eklenebilir',
));
$skipped[] = $name;
}
}
return array(
'saved_count' => count($saved),
'skipped_count' => count($skipped),
'saved' => $saved,
'skipped' => $skipped,
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Oda adındaki anahtar kelimelere göre room_type_id tahmin et
// ─────────────────────────────────────────────────────────────────────────────
private function guessRoomTypeId($roomName)
{
$lower = mb_strtolower($roomName);
// Önce çift kelimeli eşleşmeleri dene (daha spesifik)
foreach ($this->roomTypeKeywords as $keyword => $typeId) {
if (strpos($keyword, ' ') !== false && strpos($lower, $keyword) !== false) {
return $typeId;
}
}
// Sonra tek kelimeli
foreach ($this->roomTypeKeywords as $keyword => $typeId) {
if (strpos($keyword, ' ') === false && strpos($lower, $keyword) !== false) {
return $typeId;
}
}
return 24; // Default: Standard Room
}
// ─────────────────────────────────────────────────────────────────────────────
// ANA METOD: POST /bulut/property-insert
// ─────────────────────────────────────────────────────────────────────────────
public function insert(Request $request)
{
$hotelId = $request->input('hotel_id', '');
$email = $request->input('email', '');
$password = $request->input('password', '');
$name = $request->input('name', '');
$surname = $request->input('surname', '');
$propertyName = $request->input('property_name', '');
$phone = $request->input('phone', null);
$dryRun = (bool) $request->input('dry_run', false);
$checkin = $request->input('checkin', date('Y-m-d', strtotime('+30 days')));
$checkout = $request->input('checkout', date('Y-m-d', strtotime('+31 days')));
// Zorunlu alan validasyonu
$required = array('hotel_id', 'email', 'password', 'name', 'surname', 'property_name');
$missing = array();
foreach ($required as $field) {
if (!$request->input($field)) {
$missing[] = $field;
}
}
if (count($missing)) {
return response()->json(array(
'status' => false,
'message' => 'Eksik alanlar: ' . implode(', ', $missing),
), 422);
}
$report = array(
'hotel_id' => $hotelId,
'dry_run' => $dryRun,
'steps' => array(),
);
try {
DB::beginTransaction();
// ── ADIM 0: Booking.com'dan veri çek ─────────────────────────────────
$report['steps']['0_booking_fetch'] = array('status' => 'pending');
$bookingData = $this->fetchBookingData($hotelId, $checkin, $checkout);
$detail = $bookingData['detail'];
$facilData = $bookingData['facilities'];
$photoData = $bookingData['photos'];
$report['steps']['0_booking_fetch'] = array(
'status' => 'success',
'hotel_name' => isset($detail['hotel_name']) ? $detail['hotel_name'] : null,
'facility_count' => $this->countFacilities($facilData),
'photo_count' => count($this->extractPhotoUrls($photoData)),
);
// Eğer property_name boş geldiyse Booking.com adını kullan
if (!$propertyName && !empty($detail['hotel_name'])) {
$propertyName = $detail['hotel_name'];
}
if ($dryRun) {
DB::rollBack();
$report['dry_run_note'] = 'dry_run=true → DB\'ye hiçbir şey yazılmadı. Aşağıdaki veriler oluşturulacaktı.';
$report['steps']['1_user_property'] = array('status' => 'skipped (dry_run)', 'email' => $email, 'property_name' => $propertyName);
$report['steps']['2_property_info'] = array('status' => 'skipped (dry_run)', 'data' => $this->previewPropertyInfo($detail));
$report['steps']['3_contact'] = array('status' => 'skipped (dry_run)', 'phone' => $phone ?: (isset($detail['phone']) ? $detail['phone'] : null), 'email' => isset($detail['email']) ? $detail['email'] : null);
$report['steps']['4_fact_mapping'] = array('status' => 'skipped (dry_run)', 'facility_count' => $this->countFacilities($facilData));
$report['steps']['5_rooms'] = array('status' => 'skipped (dry_run)');
$report['steps']['6_photos'] = array('status' => 'skipped (dry_run)', 'photo_count' => count($this->extractPhotoUrls($photoData)));
return response()->json(array('status' => true, 'message' => 'Dry run tamamlandı', 'report' => $report));
}
// ── ADIM 1: Kullanıcı + Property oluştur ────────────────────────────
$report['steps']['1_user_property'] = array('status' => 'pending');
$userPropertyResult = $this->createUserAndProperty(
array('name' => $name, 'surname' => $surname, 'email' => $email, 'password' => $password),
$propertyName
);
$createdUserId = $userPropertyResult['user']['id'];
$createdPropertyId = $userPropertyResult['property_id'];
$report['steps']['1_user_property'] = array(
'status' => 'success',
'user_id' => $createdUserId,
'property_id' => $createdPropertyId,
'token' => $userPropertyResult['token'],
);
// ── ADIM 2: Property info güncelle ──────────────────────────────────
$report['steps']['2_property_info'] = array('status' => 'pending');
$this->updatePropertyInfo($createdPropertyId, $createdUserId, $detail);
$raw2 = isset($detail['rawData']) ? $detail['rawData'] : array();
$report['steps']['2_property_info'] = array(
'status' => 'success',
'hotel_name' => isset($detail['hotel_name']) ? $detail['hotel_name'] : null,
'rating' => isset($raw2['accuratePropertyClass']) ? (int)$raw2['accuratePropertyClass'] : null,
'country' => isset($detail['countrycode']) ? strtoupper($detail['countrycode']) : null,
'checkin' => isset($raw2['checkin']['fromTime']) ? $raw2['checkin']['fromTime'] : null,
'checkout' => isset($raw2['checkout']['untilTime']) ? $raw2['checkout']['untilTime'] : null,
'languages' => isset($detail['spoken_languages']) ? $detail['spoken_languages'] : array(),
);
// ── ADIM 3: İletişim bilgilerini kaydet ──────────────────────────────
$report['steps']['3_contact'] = array('status' => 'pending');
$this->updatePropertyContact($createdPropertyId, $createdUserId, $detail, $phone);
$report['steps']['3_contact'] = array(
'status' => 'success',
'phone' => $phone ?: (isset($detail['phone']) ? $detail['phone'] : null),
'email' => isset($detail['email']) ? $detail['email'] : null,
);
// ── ADIM 4: Facility → Fact mapping ─────────────────────────────────
$report['steps']['4_fact_mapping'] = array('status' => 'pending');
$facilityResult = $this->mapAndSaveFacilities($createdPropertyId, $createdUserId, $facilData);
$report['steps']['4_fact_mapping'] = array_merge(array('status' => 'success'), $facilityResult);
// ── ADIM 5: Oda ekle ─────────────────────────────────────────────────
$report['steps']['5_rooms'] = array('status' => 'pending');
$roomResult = $this->insertRooms($createdPropertyId, $createdUserId, $detail);
$report['steps']['5_rooms'] = array_merge(array('status' => 'success'), $roomResult);
// ── ADIM 6: Görselleri indir ve kaydet ───────────────────────────────
$report['steps']['6_photos'] = array('status' => 'pending');
$photoLimit = $request->input('photo_limit', 20);
$photoResult = $this->processAndSavePhotos($photoData, $hotelId, $createdPropertyId, $photoLimit);
$report['steps']['6_photos'] = array_merge(array('status' => 'success'), $photoResult);
DB::commit();
return response()->json(array(
'status' => true,
'message' => 'Property başarıyla oluşturuldu',
'property_id' => $createdPropertyId,
'user_id' => $createdUserId,
'report' => $report,
));
} catch (ApiErrorException $e) {
DB::rollBack();
Log::error('propertyInsert.ApiError: ' . $e->getMessage());
return response()->json(array(
'status' => false,
'message' => implode(', ', $e->getMessageArr()),
'report' => $report,
), 400);
} catch (Exception $e) {
DB::rollBack();
Log::error('propertyInsert.Error: ' . $e->getFile() . ':' . $e->getLine() . ' ' . $e->getMessage());
return response()->json(array(
'status' => false,
'message' => $e->getMessage(),
'report' => $report,
), 500);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// ADIM 6: Fotograflari indir, isle ve kaydet
// ─────────────────────────────────────────────────────────────────────────────
private function processAndSavePhotos(array $responseData, $hotelId, $propertyId, $limit = 20)
{
$photoUrls = $this->extractPhotoUrls($responseData);
if (empty($photoUrls)) {
return array(
'saved_count' => 0,
'total_in_api' => 0,
'message' => 'API yanitinda fotograf URL bulunamadi',
);
}
$uploadRoot = rtrim(Config::get('app.fileSystemDriver'), '/');
$urlPath = '/property-photos/' . $propertyId . '/';
$filePath = $uploadRoot . $urlPath;
if (!File::exists($filePath)) {
File::makeDirectory($filePath, 0777, true);
}
$hasDefault = PropertyPhoto::where('property_id', $propertyId)
->where('status', 1)
->where('is_default', 1)
->exists();
$saved = array();
$isFirstPhoto = true;
$total = min(count($photoUrls), $limit);
for ($i = 0; $i < $total; $i++) {
$photoUrl = $photoUrls[$i];
try {
$fileName = time() . '_bk' . $hotelId . '_' . $i;
$fileExt = 'jpg';
$tempPath = sys_get_temp_dir() . '/' . $fileName . '_orig.jpg';
$this->downloadRawImage($photoUrl, $tempPath);
$image = Image::make($tempPath);
$imageWidth = $image->width();
$imageHeight = $image->height();
$fileSize = (int) ceil(filesize($tempPath) / 1024);
$quality = 80;
// Orijinal — 2000px uzeriyse kucult
if ($imageHeight > 2000 || $imageWidth > 2000) {
if ($imageHeight > $imageWidth) {
$r = $imageHeight / 2000;
$image->fit((int)($imageWidth / $r), 2000);
} else {
$r = $imageWidth / 2000;
$image->fit(2000, (int)($imageHeight / $r));
}
}
$image->encode($fileExt, $quality)->orientate()->save($filePath . $fileName . '.' . $fileExt);
// _medium (max 1600x768)
$mediumImage = Image::make($tempPath);
$ratio = $imageHeight > 0 ? ($imageHeight / 768) : 1;
$resizeWidth = (int)($imageWidth / max($ratio, 1));
if ($resizeWidth > 1599) {
$mediumImage->fit(1600, 768);
} else {
$mediumImage->fit(max($resizeWidth, 1), 768);
}
$mediumImage->encode($fileExt, $quality)->orientate()->save($filePath . $fileName . '_medium.' . $fileExt);
// _thumbnail (300x300)
Image::make($tempPath)
->fit(300, 300)
->encode($fileExt, $quality)
->orientate()
->save($filePath . $fileName . '_thumbnail.' . $fileExt);
if (File::exists($tempPath)) {
File::delete($tempPath);
}
$isCompatible = ($imageWidth >= 1150 && $imageHeight >= 600) ? 1 : 0;
$isDefault = (!$hasDefault && $isFirstPhoto) ? 1 : 0;
$isFirstPhoto = false;
$photoRecord = PropertyPhoto::create(array(
'property_id' => $propertyId,
'property_photo_category_id' => 1,
'photo_path' => $urlPath,
'photo_name' => $fileName,
'file_size' => $fileSize,
'file_ext' => $fileExt,
'photo_resolution' => $imageWidth . 'x' . $imageHeight,
'is_default' => $isDefault,
'is_compatible_with_myweb_slider' => $isCompatible,
'photo_order' => 0,
'photo_rank' => 0,
'is_temp' => 1,
'status' => 1,
'created_by' => 1,
'updated_by' => 1,
'created_at' => time(),
'updated_at' => time(),
));
$saved[] = array(
'db_id' => $photoRecord->id,
'file_name' => $fileName . '.' . $fileExt,
'resolution' => $imageWidth . 'x' . $imageHeight,
'source_url' => $photoUrl,
);
} catch (Exception $e) {
Log::error('propertyInsert.photo [' . $i . ']: ' . $e->getMessage() . ' | ' . $photoUrl);
$saved[] = array('error' => $e->getMessage(), 'source_url' => $photoUrl);
}
}
$okCount = count(array_filter($saved, function ($s) {
return !isset($s['error']);
}));
return array(
'saved_count' => $okCount,
'total_in_api' => count($photoUrls),
'limit_applied' => $total,
'photos' => $saved,
);
}
private function extractPhotoUrls(array $data)
{
$urls = array();
// Format 1: data[] dizisi
if (isset($data['data']) && is_array($data['data']) && !empty($data['data'])) {
foreach ($data['data'] as $item) {
if (!is_array($item)) {
continue;
}
if (!empty($item['url_original'])) {
$urls[] = $item['url_original'];
} elseif (!empty($item['url_max'])) {
$urls[] = $item['url_max'];
} elseif (!empty($item['url'])) {
$urls[] = $item['url'];
} elseif (!empty($item['photo']['url_original'])) {
$urls[] = $item['photo']['url_original'];
}
}
}
// Format 2: data.photos[]
if (empty($urls) && isset($data['data']['photos']) && is_array($data['data']['photos'])) {
foreach ($data['data']['photos'] as $item) {
if (!empty($item['url_original'])) {
$urls[] = $item['url_original'];
} elseif (!empty($item['url'])) {
$urls[] = $item['url'];
}
}
}
// Format 3: photos[] root'ta
if (empty($urls) && isset($data['photos']) && is_array($data['photos'])) {
foreach ($data['photos'] as $item) {
if (!empty($item['url_original'])) {
$urls[] = $item['url_original'];
} elseif (!empty($item['url'])) {
$urls[] = $item['url'];
}
}
}
return $urls;
}
private function downloadRawImage($url, $savePath)
{
$client = new Client(array(
'timeout' => 30,
'verify' => false,
'headers' => array('User-Agent' => 'Mozilla/5.0'),
));
$response = $client->get($url, array('sink' => $savePath));
if ($response->getStatusCode() !== 200) {
throw new Exception('Image download failed HTTP ' . $response->getStatusCode() . ' | ' . $url);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Yardımcı: timezone string → general_timezone.id
// ─────────────────────────────────────────────────────────────────────────────
private function resolveTimezoneId($timezoneLocation)
{
$row = DB::table('general_timezone')
->where('location', $timezoneLocation)
->first();
return $row ? (string)$row->id : null;
}
// ─────────────────────────────────────────────────────────────────────────────
// Yardımcı: dry_run için property_info önizleme
// ─────────────────────────────────────────────────────────────────────────────
private function previewPropertyInfo(array $detail)
{
$raw = isset($detail['rawData']) ? $detail['rawData'] : array();
$rating = null;
foreach (array('accuratePropertyClass', 'propertyClass', 'qualityClass') as $rk) {
if (!empty($raw[$rk])) {
$rating = (int)$raw[$rk];
break;
}
}
return array(
'name' => isset($detail['hotel_name']) ? $detail['hotel_name'] : null,
'rating' => $rating,
'country' => isset($detail['countrycode']) ? strtoupper($detail['countrycode']) : null,
'address' => isset($detail['address']) ? $detail['address'] : null,
'city' => isset($detail['city_name_en']) ? $detail['city_name_en'] : null,
'district' => isset($detail['district']) ? $detail['district'] : null,
'zip' => isset($detail['zip']) ? $detail['zip'] : null,
'latitude' => isset($detail['latitude']) ? $detail['latitude'] : null,
'longitude' => isset($detail['longitude']) ? $detail['longitude'] : null,
'timezone' => isset($detail['timezone']) ? $detail['timezone'] : null,
'checkin' => isset($raw['checkin']['fromTime']) ? $raw['checkin']['fromTime'] : null,
'checkout' => isset($raw['checkout']['untilTime']) ? $raw['checkout']['untilTime'] : null,
'languages' => isset($detail['spoken_languages']) ? $detail['spoken_languages'] : array(),
'review_score' => isset($raw['reviewScore']) ? $raw['reviewScore'] : null,
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Yardımcı: facility sayısını döndür
// ─────────────────────────────────────────────────────────────────────────────
private function countFacilities(array $facilData)
{
$count = 0;
if (isset($facilData['facilities'])) {
foreach ($facilData['facilities'] as $fac) {
$count += count(isset($fac['instances']) ? $fac['instances'] : array());
}
}
return $count;
}
// ─────────────────────────────────────────────────────────────────────────────
// GET /bulut/property-check?email=...
// Verilen email'e ait kullanıcı ve property bilgilerini döndür
// ─────────────────────────────────────────────────────────────────────────────
public function check(Request $request)
{
$email = trim($request->input('email', ''));
if (!$email) {
return response()->json(array('status' => false, 'message' => 'email parametresi gerekli'), 422);
}
$user = DB::table('user')->where('email', $email)->first();
if (!$user) {
return response()->json(array(
'status' => false,
'message' => 'Kullanıcı bulunamadı',
'email' => $email,
));
}
// Kullanıcıya bağlı property'ler
$mappings = DB::table('user_property_mapping as upm')
->join('property as p', 'p.id', '=', 'upm.property_id')
->where('upm.user_id', $user->id)
->select(array(
'p.id as property_id',
'p.name as property_name',
'p.status as property_status',
'upm.created_at',
))
->get();
$properties = array();
foreach ($mappings as $m) {
$roomCount = DB::table('property_room')->where('property_id', $m->property_id)->count();
$photoCount = DB::table('property_photo')->where('property_id', $m->property_id)->count();
$factCount = DB::table('property_fact_mapping')->where('property_id', $m->property_id)->count();
$properties[] = array(
'property_id' => $m->property_id,
'property_name' => $m->property_name,
'status' => $m->property_status,
'room_count' => $roomCount,
'photo_count' => $photoCount,
'fact_count' => $factCount,
'created_at' => date('Y-m-d H:i:s', $m->created_at),
);
}
return response()->json(array(
'status' => true,
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name . ' ' . $user->surname,
'user_status' => $user->status,
'properties' => $properties,
));
}
// ─────────────────────────────────────────────────────────────────────────────
// DELETE /bulut/property-delete
// Body: { "property_id": 123 } veya { "email": "..." } (emailde tüm property'ler)
// Odalar, fotoğraflar (dosya dahil), fact mapping, iletişim, user-property mapping,
// property kaydı ve kullanıcı silinir.
// ─────────────────────────────────────────────────────────────────────────────
public function delete(Request $request)
{
$propertyId = $request->input('property_id');
$email = trim($request->input('email', ''));
if (!$propertyId && !$email) {
return response()->json(array('status' => false, 'message' => 'property_id veya email gerekli'), 422);
}
$report = array();
try {
DB::beginTransaction();
// Email ile geldiyse önce kullanıcı + property_id'leri bul
$userIdToDelete = null;
$propertyIds = array();
if ($email) {
$user = DB::table('user')->where('email', $email)->first();
if (!$user) {
return response()->json(array('status' => false, 'message' => 'Kullanıcı bulunamadı'));
}
$userIdToDelete = $user->id;
$pids = DB::table('user_property_mapping')
->where('user_id', $userIdToDelete)
->pluck('property_id')
->toArray();
$propertyIds = $pids;
} else {
$propertyIds = array((int)$propertyId);
// Bu property'nin tek sahibi varsa onu da sil
$ownerCount = DB::table('user_property_mapping')
->where('property_id', $propertyId)
->count();
if ($ownerCount === 1) {
$userIdToDelete = DB::table('user_property_mapping')
->where('property_id', $propertyId)
->value('user_id');
}
}
foreach ($propertyIds as $pid) {
$deleted = array('property_id' => $pid);
// 1. Fotoğraf dosyalarını sil
$photos = DB::table('property_photo')->where('property_id', $pid)->get();
$filesDeleted = 0;
$uploadRoot = rtrim(Config::get('app.fileSystemDriver'), '/');
foreach ($photos as $photo) {
$base = $uploadRoot . $photo->photo_path . $photo->photo_name;
foreach (array('', '_medium', '_thumbnail') as $suffix) {
$path = $base . $suffix . '.' . $photo->file_ext;
if (File::exists($path)) {
File::delete($path);
$filesDeleted++;
}
}
}
$deleted['photos_db'] = DB::table('property_photo')->where('property_id', $pid)->delete();
$deleted['photos_files'] = $filesDeleted;
// 2. Oda ilişkileri
$roomIds = DB::table('property_room')->where('property_id', $pid)->pluck('id')->toArray();
if (!empty($roomIds)) {
DB::table('property_room_bed')->whereIn('room_id', $roomIds)->delete();
DB::table('property_room_view_mapping')->whereIn('room_id', $roomIds)->delete();
DB::table('property_room_fact_mapping')->whereIn('room_id', $roomIds)->delete();
DB::table('property_room_photo_mapping')->whereIn('room_id', $roomIds)->delete();
}
$deleted['rooms'] = DB::table('property_room')->where('property_id', $pid)->delete();
// 3. Fact mapping
$deleted['fact_mapping'] = DB::table('property_fact_mapping')->where('property_id', $pid)->delete();
// 4. İletişim
$deleted['contact'] = DB::table('property_contact')->where('property_id', $pid)->delete();
// 5. Property info tabloları
DB::table('property_additional_info')->where('property_id', $pid)->delete();
DB::table('property_language_spoken')->where('property_id', $pid)->delete();
// 6. User-property mapping
$deleted['user_mapping'] = DB::table('user_property_mapping')->where('property_id', $pid)->delete();
// 7. Property kaydı
$deleted['property'] = DB::table('property')->where('id', $pid)->delete();
$report[] = $deleted;
}
// 8. Kullanıcıyı sil (başka property'si yoksa)
if ($userIdToDelete) {
$remaining = DB::table('user_property_mapping')->where('user_id', $userIdToDelete)->count();
if ($remaining === 0) {
DB::table('api_access_token')->where('user_id', $userIdToDelete)->delete();
DB::table('user')->where('id', $userIdToDelete)->delete();
$report['user_deleted'] = $userIdToDelete;
}
}
DB::commit();
return response()->json(array(
'status' => true,
'message' => 'Silme tamamlandı',
'report' => $report,
));
} catch (Exception $e) {
DB::rollBack();
Log::error('propertyDelete.Error: ' . $e->getFile() . ':' . $e->getLine() . ' ' . $e->getMessage());
return response()->json(array(
'status' => false,
'message' => $e->getMessage(),
'report' => $report,
), 500);
}
}
}