- getAvailableTimes() takes optional exclude_booking_id so a guest's own booking isn't counted as a conflict when editing — original time now reappears when extending services - Manage-booking AJAX passes manage_token; server resolves to booking_id - manage_booking_process uses the new param instead of the date-swap workaround (removes a small race-condition risk) - Modify-booking emails (no/en/hu) now include Name/Email/Phone rows and the 24h cancellation policy, matching the original booking email
970 lines
34 KiB
PHP
Executable File
970 lines
34 KiB
PHP
Executable File
<?php
|
|
class Service_model extends CI_Model {
|
|
|
|
function __construct(){
|
|
parent::__construct();
|
|
}
|
|
|
|
public function createService($serviceArray){
|
|
$this->load->database();
|
|
$query = $this->db->query('INSERT INTO services (
|
|
service_type,
|
|
service_category_no,
|
|
service_category_en,
|
|
service_category_hu,
|
|
service_name_no,
|
|
service_name_en,
|
|
service_name_hu,
|
|
service_description_no,
|
|
service_description_en,
|
|
service_description_hu,
|
|
service_price,
|
|
service_time,
|
|
service_category_id,
|
|
is_enabled
|
|
) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', array(
|
|
$serviceArray['service_type'],
|
|
$serviceArray['service_category_no'],
|
|
$serviceArray['service_category_en'],
|
|
$serviceArray['service_category_hu'],
|
|
$serviceArray['service_name_no'],
|
|
$serviceArray['service_name_en'],
|
|
$serviceArray['service_name_hu'],
|
|
$serviceArray['service_description_no'],
|
|
$serviceArray['service_description_en'],
|
|
$serviceArray['service_description_hu'],
|
|
$serviceArray['service_price'],
|
|
$serviceArray['service_time'],
|
|
$serviceArray['service_category_id'],
|
|
$serviceArray['is_enabled']
|
|
));
|
|
}
|
|
|
|
public function createServiceCategory($serviceCategoryArray){
|
|
$this->load->database();
|
|
$query = $this->db->query('INSERT INTO service_categories (
|
|
serv_cat_slug,
|
|
serv_cat_name
|
|
) VALUES(?, ?)', array(
|
|
$serviceCategoryArray['serv_cat_slug'],
|
|
$serviceCategoryArray['serv_cat_name']
|
|
));
|
|
}
|
|
|
|
public function updateService($service_id, $serviceArray){
|
|
$this->load->database();
|
|
$this->db->where('service_id', $service_id);
|
|
$this->db->update('services', $serviceArray);
|
|
}
|
|
|
|
public function updateServiceCategory($service_category_id, $serviceCategoryArray){
|
|
$this->load->database();
|
|
$this->db->where('service_category_id', $service_category_id);
|
|
$this->db->update('service_categories', $serviceCategoryArray);
|
|
}
|
|
|
|
public function createWorker($workerArray){
|
|
$this->load->database();
|
|
$query = $this->db->query('INSERT INTO workers (
|
|
worker_name,
|
|
worker_profile_img,
|
|
worker_info,
|
|
is_beauty,
|
|
is_barber,
|
|
service_category_id
|
|
) VALUES(?, ?, ?, ?, ?, ?)', array(
|
|
$workerArray['worker_name'],
|
|
$workerArray['worker_profile_img'],
|
|
$workerArray['worker_info'],
|
|
$workerArray['is_beauty'],
|
|
$workerArray['is_barber'],
|
|
$workerArray['service_category_id']
|
|
));
|
|
}
|
|
|
|
public function updateWorker($worker_id, $workerArray){
|
|
$this->load->database();
|
|
$this->db->where('worker_id', $worker_id);
|
|
$this->db->update('workers', $workerArray);
|
|
}
|
|
|
|
public function updateBooking($booking_id, $bookingArray){
|
|
$this->load->database();
|
|
$this->db->where('booking_id', $booking_id);
|
|
$this->db->update('bookings', $bookingArray);
|
|
}
|
|
|
|
public function getAllServiceByServiceType($service_type, $lang){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM services JOIN service_categories ON services.service_category_id = service_categories.service_category_id WHERE service_type = ? AND services.is_enabled = "1" AND services.is_deleted != "1" ORDER BY service_id ASC', array($service_type));
|
|
$serviceArray = array();
|
|
if($query->num_rows() > 0){
|
|
foreach($query->result() as $resultItem){
|
|
|
|
$service_category = '';
|
|
$service_name = '';
|
|
$service_description = '';
|
|
|
|
switch($lang){
|
|
case 'no':
|
|
$service_category = $resultItem->service_category_no;
|
|
$service_name = $resultItem->service_name_no;
|
|
$service_description = $resultItem->service_description_no;
|
|
break;
|
|
case 'en':
|
|
$service_category = $resultItem->service_category_en;
|
|
$service_name = $resultItem->service_name_en;
|
|
$service_description = $resultItem->service_description_en;
|
|
break;
|
|
case 'hu':
|
|
$service_category = $resultItem->service_category_hu;
|
|
$service_name = $resultItem->service_name_hu;
|
|
$service_description = $resultItem->service_description_hu;
|
|
break;
|
|
}
|
|
|
|
$resultItemTmp = array(
|
|
'service_id' => $resultItem->service_id,
|
|
'service_type' => $resultItem->service_type,
|
|
'service_category' => $service_category,
|
|
'service_name' => $service_name,
|
|
'service_description' => $service_description,
|
|
'service_price' => $resultItem->service_price,
|
|
'service_time' => $resultItem->service_time,
|
|
'serv_cat_slug' => $resultItem->serv_cat_slug,
|
|
'service_category_id' => $resultItem->service_category_id
|
|
);
|
|
|
|
$serviceArray[] = (object)$resultItemTmp;
|
|
}
|
|
}
|
|
return $serviceArray;
|
|
}
|
|
|
|
public function getServiceById($service_id){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM services WHERE service_id = ? LIMIT 1', array($service_id));
|
|
if($query->num_rows() > 0){
|
|
return $query->result()[0];
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public function getServiceCategoryById($service_category_id){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM service_categories WHERE service_category_id = ? LIMIT 1', array($service_category_id));
|
|
if($query->num_rows() > 0){
|
|
return $query->result()[0];
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public function getAllServices(){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM services JOIN service_categories ON services.service_category_id = service_categories.service_category_id WHERE services.is_deleted != "1" ORDER BY service_type ASC, service_name_hu ASC;');
|
|
if($query->num_rows() > 0){
|
|
return $query->result();
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public function getAllServiceCategories(){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM service_categories WHERE is_deleted != "1" ORDER BY service_category_id;');
|
|
if($query->num_rows() > 0){
|
|
return $query->result();
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public function getAvailableServices(){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM services WHERE is_enabled = "1" AND is_deleted != "1" ORDER BY service_name_hu ASC;');
|
|
|
|
if($query->num_rows() > 0){
|
|
|
|
$serviceArray = array();
|
|
foreach($query->result() as $resultItem){
|
|
$serviceTmp = array(
|
|
'service_name' => $resultItem->service_name_hu.' ('.$resultItem->service_type.')',
|
|
'service_id' => $resultItem->service_id
|
|
);
|
|
|
|
$serviceArray[] = $serviceTmp;
|
|
}
|
|
return $serviceArray;
|
|
}
|
|
}
|
|
|
|
public function getAllWorkers(){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM workers JOIN service_categories ON workers.service_category_id = service_categories.service_category_id WHERE workers.is_deleted != "1" ORDER BY worker_name ASC;');
|
|
return $query->result();
|
|
}
|
|
|
|
public function getWorkersByCategorySlug($serv_cat_slug){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM workers JOIN service_categories ON workers.service_category_id = service_categories.service_category_id WHERE workers.is_deleted != "1" AND serv_cat_slug = ? ORDER BY worker_name ASC', array($serv_cat_slug));
|
|
return $query->result();
|
|
}
|
|
|
|
public function getActiveWorkers($subpage = ''){
|
|
$this->load->database();
|
|
$subpageString = '';
|
|
|
|
if($subpage == 'beauty'){
|
|
$subpageString = ' AND is_beauty = "1"';
|
|
}
|
|
else if($subpage == 'barber'){
|
|
$subpageString = ' AND is_barber = "1"';
|
|
}
|
|
else{
|
|
$subpageString = '';
|
|
}
|
|
|
|
$query = $this->db->query('SELECT * FROM workers WHERE is_active = "1" '.$subpageString.' AND is_deleted != "1" ORDER BY worker_name ASC;');
|
|
return $query->result();
|
|
}
|
|
|
|
public function deleteBooking($booking_id){
|
|
$this->load->database();
|
|
$query = $this->db->query('DELETE FROM bookings WHERE booking_id = ?', array($booking_id));
|
|
}
|
|
|
|
public function getAllBookings(){
|
|
$this->load->database();
|
|
$this->load->model('Service_model');
|
|
|
|
$query = $this->db->query('SELECT * FROM bookings JOIN workers ON bookings.worker_id = workers.worker_id ORDER BY booking_date DESC, booking_start_time ASC;');
|
|
|
|
if($query->num_rows() > 0){
|
|
$resultsArray = array();
|
|
foreach($query->result() as $resultItem){
|
|
$resultTmp = (array)$resultItem;
|
|
if($resultTmp['service_ids'] != ''){
|
|
$servicesArray = unserialize($resultTmp['service_ids']);
|
|
if(is_array($servicesArray)){
|
|
$services = array();
|
|
foreach($servicesArray as $servicesArrayItem){
|
|
$selectedService = $this->Service_model->getServiceById($servicesArrayItem);
|
|
$services[] = (object)array(
|
|
'service_type' => $selectedService->service_type,
|
|
'service_name' => $selectedService->service_name_hu,
|
|
'service_price' => $selectedService->service_price,
|
|
'service_time' => $selectedService->service_time
|
|
);
|
|
}
|
|
$resultTmp['services'] = $services;
|
|
}
|
|
else{
|
|
$resultTmp['services'] = array();
|
|
}
|
|
}
|
|
else{
|
|
$resultTmp['services'] = array();
|
|
|
|
}
|
|
$resultsArray[] = (object)$resultTmp;
|
|
}
|
|
|
|
|
|
return $resultsArray;
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public function getActualBookings(){
|
|
$this->load->database();
|
|
$this->load->model('Service_model');
|
|
|
|
$query = $this->db->query('SELECT * FROM bookings JOIN workers ON bookings.worker_id = workers.worker_id WHERE booking_date >= CURDATE() ORDER BY booking_date DESC, booking_start_time ASC;');
|
|
|
|
if($query->num_rows() > 0){
|
|
$resultsArray = array();
|
|
foreach($query->result() as $resultItem){
|
|
$resultTmp = (array)$resultItem;
|
|
if($resultTmp['service_ids'] != ''){
|
|
$servicesArray = unserialize($resultTmp['service_ids']);
|
|
if(is_array($servicesArray)){
|
|
$services = array();
|
|
foreach($servicesArray as $servicesArrayItem){
|
|
$selectedService = $this->Service_model->getServiceById($servicesArrayItem);
|
|
$services[] = (object)array(
|
|
'service_type' => $selectedService->service_type,
|
|
'service_name' => $selectedService->service_name_hu,
|
|
'service_price' => $selectedService->service_price,
|
|
'service_time' => $selectedService->service_time
|
|
);
|
|
}
|
|
$resultTmp['services'] = $services;
|
|
}
|
|
else{
|
|
$resultTmp['services'] = array();
|
|
}
|
|
}
|
|
else{
|
|
$resultTmp['services'] = array();
|
|
|
|
}
|
|
$resultsArray[] = (object)$resultTmp;
|
|
}
|
|
|
|
|
|
return $resultsArray;
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public function getActualBookingsByWorkerID($workerID){
|
|
$this->load->database();
|
|
$this->load->model('Service_model');
|
|
|
|
$query = $this->db->query('SELECT * FROM bookings JOIN workers ON bookings.worker_id = workers.worker_id WHERE booking_date >= CURDATE() AND bookings.worker_id = ? ORDER BY booking_date DESC, booking_start_time ASC', array($workerID));
|
|
|
|
if($query->num_rows() > 0){
|
|
$resultsArray = array();
|
|
foreach($query->result() as $resultItem){
|
|
$resultTmp = (array)$resultItem;
|
|
if($resultTmp['service_ids'] != ''){
|
|
$servicesArray = unserialize($resultTmp['service_ids']);
|
|
if(is_array($servicesArray)){
|
|
$services = array();
|
|
foreach($servicesArray as $servicesArrayItem){
|
|
$selectedService = $this->Service_model->getServiceById($servicesArrayItem);
|
|
$services[] = (object)array(
|
|
'service_type' => $selectedService->service_type,
|
|
'service_name' => $selectedService->service_name_hu,
|
|
'service_price' => $selectedService->service_price,
|
|
'service_time' => $selectedService->service_time
|
|
);
|
|
}
|
|
$resultTmp['services'] = $services;
|
|
}
|
|
else{
|
|
$resultTmp['services'] = array();
|
|
}
|
|
}
|
|
else{
|
|
$resultTmp['services'] = array();
|
|
|
|
}
|
|
$resultsArray[] = (object)$resultTmp;
|
|
}
|
|
|
|
|
|
return $resultsArray;
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public function getBookingById($booking_id){
|
|
$this->load->database();
|
|
$this->load->model('Service_model');
|
|
|
|
$query = $this->db->query('SELECT * FROM bookings WHERE booking_id = ? LIMIT 1', array($booking_id));
|
|
if($query->num_rows() > 0){
|
|
return $query->result()[0];
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public function getWorkerById($worker_id){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM workers WHERE worker_id = ? LIMIT 1', array($worker_id));
|
|
if($query->num_rows() > 0){
|
|
return $query->result()[0];
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
public function getAvailableTimes($worker_id, $selectedDate, $servicelength, $exclude_booking_id = null) {
|
|
$this->load->database();
|
|
|
|
// múltbeli napra ne lehessen foglalni
|
|
if (strtotime($selectedDate) < strtotime(date('Y-m-d'))) {
|
|
return [];
|
|
}
|
|
|
|
// max 3 hónappal előre
|
|
$today = new DateTime();
|
|
$maxDate = (clone $today)->modify('+3 months');
|
|
$selected = new DateTime($selectedDate);
|
|
|
|
if ($selected > $maxDate) {
|
|
return [];
|
|
}
|
|
|
|
$ts = strtotime($selectedDate);
|
|
$debugDate = date('Y-m-d (w)', $ts) . ' - ' . date('l', $ts);
|
|
log_message('error', "📅 DEBUG DATE CHECK for '{$selectedDate}': {$debugDate}");
|
|
log_message('error', "⏱ Timezone: " . date_default_timezone_get());
|
|
|
|
$serviceLengthTime = strtotime($servicelength);
|
|
$availableTimeArray = [];
|
|
|
|
// --- Override ellenőrzés ---
|
|
$overrideQuery = $this->db->query("
|
|
SELECT * FROM worker_schedule_overrides
|
|
WHERE worker_id = ? AND date = ?
|
|
LIMIT 1
|
|
", array($worker_id, $selectedDate));
|
|
|
|
if ($overrideQuery->num_rows() > 0) {
|
|
$override = $overrideQuery->row();
|
|
|
|
if ($override->is_day_off) return [];
|
|
|
|
$dayStartTime = new DateTime($selectedDate.' '.$override->start_time, new DateTimeZone('Europe/Oslo'));
|
|
$finishTime = new DateTime($selectedDate.' '.$override->end_time, new DateTimeZone('Europe/Oslo'));
|
|
} else {
|
|
$weekday = date('w', strtotime($selectedDate)); // 0 = Sunday, 6 = Saturday
|
|
$weekNumber = date('W', strtotime($selectedDate));
|
|
$isOddWeek = $weekNumber % 2 !== 0;
|
|
|
|
$scheduleQuery = $this->db->query("
|
|
SELECT * FROM worker_schedule
|
|
WHERE worker_id = ?
|
|
AND weekday = ?
|
|
AND (
|
|
is_alternate_week = 0
|
|
OR (is_alternate_week = 1 AND " . ($isOddWeek ? "1" : "0") . ")
|
|
)
|
|
", array($worker_id, $weekday));
|
|
|
|
if ($scheduleQuery->num_rows() == 0) {
|
|
return [];
|
|
}
|
|
|
|
$schedule = $scheduleQuery->row();
|
|
$dayStartTime = new DateTime($selectedDate . ' ' . $schedule->start_time, new DateTimeZone('Europe/Oslo'));
|
|
$finishTime = new DateTime($selectedDate . ' ' . $schedule->end_time, new DateTimeZone('Europe/Oslo'));
|
|
}
|
|
|
|
// --- ÚJ: ma foglalva csak (következő 15 perces blokk + 1 óra) UTÁN legyen időpont ---
|
|
if ($selectedDate == date('Y-m-d')) {
|
|
// használd a helyi (Oslo) időt, ne az esetleges UTC defaultot
|
|
$now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
|
|
|
|
// kerekítés a következő 15 perces blokkra
|
|
$minutes = (int)$now->format('i');
|
|
$mod = $minutes % 15;
|
|
if ($mod !== 0) {
|
|
$now->modify('+' . (15 - $mod) . ' minutes');
|
|
}
|
|
|
|
// erre dobunk +1 órát
|
|
$now->modify('+1 hour');
|
|
$now->setTime((int)$now->format('H'), (int)$now->format('i'), 0);
|
|
// ha ez később van, mint a munkanap kezdete, toljuk fel a nap kezdetét
|
|
if ($now > $dayStartTime) {
|
|
$dayStartTime = clone $now;
|
|
}
|
|
|
|
log_message('error', '⏳ Min start (next 15min +1h, Europe/Oslo): ' . $dayStartTime->format('Y-m-d H:i:s'));
|
|
}
|
|
|
|
// --- Lunch break setup ---
|
|
// Pre-fetch all bookings for the day so we can do hypothetical checks per slot
|
|
$workerRow = $this->db->query("SELECT lunch_window_start, lunch_window_end, lunch_preferred_time FROM workers WHERE worker_id = ? LIMIT 1", array($worker_id))->row();
|
|
$shiftSeconds = $finishTime->getTimestamp() - $dayStartTime->getTimestamp();
|
|
$lunchRequired = $workerRow && $workerRow->lunch_window_start && $workerRow->lunch_window_end && $shiftSeconds >= 6 * 3600;
|
|
|
|
$allBookings = [];
|
|
if ($lunchRequired) {
|
|
if ($exclude_booking_id !== null) {
|
|
$allBookings = $this->db->query("
|
|
SELECT booking_start_time, booking_finish_time FROM bookings
|
|
WHERE booking_date = ? AND worker_id = ? AND booking_id != ?
|
|
", array($selectedDate, $worker_id, $exclude_booking_id))->result();
|
|
} else {
|
|
$allBookings = $this->db->query("
|
|
SELECT booking_start_time, booking_finish_time FROM bookings
|
|
WHERE booking_date = ? AND worker_id = ?
|
|
", array($selectedDate, $worker_id))->result();
|
|
}
|
|
}
|
|
|
|
$interval = new DateInterval('PT15M');
|
|
list($h, $m) = explode(':', $servicelength);
|
|
$serviceDuration = new DateInterval('PT' . (int)$h . 'H' . (int)$m . 'M');
|
|
|
|
for ($current = clone $dayStartTime; $current < $finishTime; $current->add($interval)) {
|
|
$slotEnd = clone $current;
|
|
$slotEnd->add($serviceDuration);
|
|
if ($slotEnd > $finishTime) break;
|
|
|
|
$startTimeStr = $current->format('H:i:s');
|
|
$endTimeStr = $slotEnd->format('H:i:s');
|
|
|
|
if ($exclude_booking_id !== null) {
|
|
$bookingQuery = $this->db->query("
|
|
SELECT * FROM bookings
|
|
WHERE booking_date = ?
|
|
AND worker_id = ?
|
|
AND booking_id != ?
|
|
AND booking_start_time < ? AND booking_finish_time > ?
|
|
", array($selectedDate, $worker_id, $exclude_booking_id, $endTimeStr, $startTimeStr));
|
|
} else {
|
|
$bookingQuery = $this->db->query("
|
|
SELECT * FROM bookings
|
|
WHERE booking_date = ?
|
|
AND worker_id = ?
|
|
AND booking_start_time < ? AND booking_finish_time > ?
|
|
", array($selectedDate, $worker_id, $endTimeStr, $startTimeStr));
|
|
}
|
|
|
|
if ($bookingQuery->num_rows() > 0) continue; // already booked
|
|
|
|
if ($lunchRequired) {
|
|
// Check: if we book this slot, can a 30-min lunch break still fit somewhere?
|
|
$hypo = array_merge((array)$allBookings, [(object)[
|
|
'booking_start_time' => $startTimeStr,
|
|
'booking_finish_time' => $endTimeStr,
|
|
]]);
|
|
$canStillBreak = $this->computeLunchBreak(
|
|
$workerRow->lunch_window_start, $workerRow->lunch_window_end,
|
|
$selectedDate, $hypo, $finishTime, $dayStartTime,
|
|
$workerRow->lunch_preferred_time
|
|
) !== null;
|
|
if (!$canStillBreak) continue; // booking this slot would eliminate the only break opportunity
|
|
}
|
|
|
|
$availableTimeArray[] = $startTimeStr;
|
|
|
|
log_message('debug', 'Selected Date: ' . $selectedDate);
|
|
log_message('debug', 'Weekday: ' . (isset($weekday) ? $weekday : 'N/A'));
|
|
log_message('debug', 'Worker ID: ' . $worker_id);
|
|
}
|
|
|
|
return $availableTimeArray;
|
|
}
|
|
|
|
|
|
|
|
|
|
public function createBooking($bookingArray){
|
|
$this->load->database();
|
|
$query = $this->db->query("INSERT INTO bookings (
|
|
guest_name,
|
|
guest_email,
|
|
guest_phone,
|
|
worker_id,
|
|
booking_date,
|
|
booking_start_time,
|
|
booking_finish_time,
|
|
service_ids,
|
|
guest_confirmed,
|
|
guest_confirm_code,
|
|
manage_token
|
|
) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", array(
|
|
$bookingArray['guest_name'],
|
|
$bookingArray['guest_email'],
|
|
$bookingArray['guest_phone'],
|
|
$bookingArray['worker_id'],
|
|
$bookingArray['booking_date'],
|
|
$bookingArray['booking_start_time'],
|
|
$bookingArray['booking_finish_time'],
|
|
$bookingArray['service_ids'],
|
|
$bookingArray['guest_confirmed'],
|
|
$bookingArray['guest_confirm_code'],
|
|
$bookingArray['manage_token']
|
|
));
|
|
}
|
|
|
|
public function getBookingByToken($token){
|
|
$this->load->database();
|
|
$query = $this->db->query('SELECT * FROM bookings WHERE manage_token = ? LIMIT 1', array($token));
|
|
return $query->num_rows() > 0 ? $query->result()[0] : false;
|
|
}
|
|
|
|
public function updateBookingCalEvents($booking_id, $worker_event_id, $owner_event_id){
|
|
$this->load->database();
|
|
$this->db->query("UPDATE bookings SET gcal_event_id_worker = ?, gcal_event_id_owner = ? WHERE booking_id = ?", array($worker_event_id, $owner_event_id, $booking_id));
|
|
}
|
|
|
|
public function getWorkerSchedule($worker_id) {
|
|
return $this->db
|
|
->where('worker_id', $worker_id)
|
|
->order_by('weekday', 'ASC')
|
|
->get('worker_schedule')
|
|
->result();
|
|
}
|
|
|
|
public function clearWorkerSchedule($worker_id) {
|
|
$this->load->database();
|
|
$this->db->where('worker_id', $worker_id)->delete('worker_schedule');
|
|
}
|
|
|
|
public function addWorkerSchedule($data) {
|
|
$this->db->insert('worker_schedule', $data);
|
|
}
|
|
|
|
public function getAllWorkerSchedules() {
|
|
$query = $this->db->query('SELECT * FROM worker_schedule');
|
|
return $query->result();
|
|
}
|
|
|
|
public function getAllWorkerOverrides() {
|
|
$query = $this->db->query('SELECT * FROM worker_schedule_overrides');
|
|
return $query->result();
|
|
}
|
|
|
|
public function getWorkerScheduleOverrides() {
|
|
$this->load->database();
|
|
$query = $this->db->query("
|
|
SELECT o.*, w.worker_name
|
|
FROM worker_schedule_overrides o
|
|
JOIN workers w ON o.worker_id = w.worker_id
|
|
ORDER BY o.date DESC
|
|
");
|
|
return $query->result();
|
|
}
|
|
|
|
public function createWorkerScheduleOverride($override) {
|
|
$this->db->insert('worker_schedule_overrides', $override);
|
|
}
|
|
|
|
public function getBookingsForWorkerDay($worker_id, $date) {
|
|
$this->load->database();
|
|
return $this->db->query("
|
|
SELECT booking_start_time, booking_finish_time FROM bookings
|
|
WHERE worker_id = ? AND booking_date = ?
|
|
ORDER BY booking_start_time
|
|
", [$worker_id, $date])->result();
|
|
}
|
|
|
|
public function getLunchGcalEventId($worker_id, $date) {
|
|
$this->load->database();
|
|
$row = $this->db->query("SELECT gcal_event_id FROM worker_lunch_gcal_events WHERE worker_id = ? AND date = ? LIMIT 1", [$worker_id, $date])->row();
|
|
return $row ? $row->gcal_event_id : null;
|
|
}
|
|
|
|
public function upsertLunchGcalEventId($worker_id, $date, $event_id) {
|
|
$this->load->database();
|
|
$existing = $this->getLunchGcalEventId($worker_id, $date);
|
|
if ($existing) {
|
|
$this->db->where('worker_id', $worker_id)->where('date', $date)->update('worker_lunch_gcal_events', ['gcal_event_id' => $event_id]);
|
|
} else {
|
|
$this->db->insert('worker_lunch_gcal_events', ['worker_id' => $worker_id, 'date' => $date, 'gcal_event_id' => $event_id]);
|
|
}
|
|
}
|
|
|
|
public function deleteLunchGcalEventRecord($worker_id, $date) {
|
|
$this->load->database();
|
|
$this->db->where('worker_id', $worker_id)->where('date', $date)->delete('worker_lunch_gcal_events');
|
|
}
|
|
|
|
public function getBookingsForWorkerMonth($worker_id, $year, $month) {
|
|
$this->load->database();
|
|
$yearMonth = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT);
|
|
$query = $this->db->query("
|
|
SELECT booking_date, booking_start_time, booking_finish_time FROM bookings
|
|
WHERE worker_id = ? AND DATE_FORMAT(booking_date,'%Y-%m') = ?
|
|
ORDER BY booking_date, booking_start_time
|
|
", [$worker_id, $yearMonth]);
|
|
$result = [];
|
|
foreach ($query->result() as $row) {
|
|
$result[$row->booking_date][] = $row;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public function computeLunchBreak($winStart, $winEnd, $date, $dayBookings, $shiftEnd, $shiftStart = null, $preferredTime = null) {
|
|
// Lunch break only applies for shifts of 6+ hours
|
|
if ($shiftStart && ($shiftEnd->getTimestamp() - $shiftStart->getTimestamp()) < 6 * 3600) {
|
|
return null;
|
|
}
|
|
|
|
$lunchDuration = new DateInterval('PT30M');
|
|
$lunchStep = new DateInterval('PT15M');
|
|
$tz = new DateTimeZone('Europe/Oslo');
|
|
$winStartDt = new DateTime($date . ' ' . $winStart, $tz);
|
|
$winEndDt = new DateTime($date . ' ' . $winEnd, $tz);
|
|
|
|
// Clamp window to actual shift hours
|
|
if ($shiftStart && $winStartDt < $shiftStart) $winStartDt = clone $shiftStart;
|
|
if ($winEndDt > $shiftEnd) $winEndDt = clone $shiftEnd;
|
|
|
|
$scanEnd = (clone $winEndDt)->sub($lunchDuration);
|
|
|
|
$isFree = function($candidate) use ($lunchDuration, $dayBookings, $date) {
|
|
$tz = new DateTimeZone('Europe/Oslo');
|
|
$candEnd = (clone $candidate)->add($lunchDuration);
|
|
foreach ($dayBookings as $bk) {
|
|
$bkS = new DateTime($date . ' ' . $bk->booking_start_time, $tz);
|
|
$bkE = new DateTime($date . ' ' . $bk->booking_finish_time, $tz);
|
|
if ($bkS < $candEnd && $bkE > $candidate) return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// Collect all free candidate slots within window
|
|
$freeSlots = [];
|
|
for ($c = clone $winStartDt; $c <= $scanEnd; $c->add($lunchStep)) {
|
|
if ($isFree($c)) $freeSlots[] = clone $c;
|
|
}
|
|
|
|
if (!empty($freeSlots)) {
|
|
// Pick the slot closest to preferred time (or start of window if no preference)
|
|
$prefDt = $preferredTime
|
|
? new DateTime($date . ' ' . $preferredTime, new DateTimeZone('Europe/Oslo'))
|
|
: clone $winStartDt;
|
|
$best = null;
|
|
$bestDiff = PHP_INT_MAX;
|
|
foreach ($freeSlots as $slot) {
|
|
$diff = abs($slot->getTimestamp() - $prefDt->getTimestamp());
|
|
if ($diff < $bestDiff) { $bestDiff = $diff; $best = $slot; }
|
|
}
|
|
$bestEnd = (clone $best)->add($lunchDuration);
|
|
return ['start' => $best->format('H:i'), 'end' => $bestEnd->format('H:i')];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getWorkerOverridesByMonth($worker_id, $year, $month) {
|
|
$this->load->database();
|
|
$yearMonth = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT);
|
|
$query = $this->db->query(
|
|
"SELECT * FROM worker_schedule_overrides WHERE worker_id = ? AND DATE_FORMAT(date,'%Y-%m') = ?",
|
|
[$worker_id, $yearMonth]
|
|
);
|
|
$result = [];
|
|
foreach ($query->result() as $row) {
|
|
$result[$row->date] = $row;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public function getWorkerScheduleOverrideByDate($worker_id, $date) {
|
|
$this->load->database();
|
|
$this->db->where('worker_id', $worker_id);
|
|
$this->db->where('date', $date);
|
|
$this->db->limit(1);
|
|
return $this->db->get('worker_schedule_overrides')->row();
|
|
}
|
|
|
|
public function upsertWorkerScheduleOverride($worker_id, $date, $data) {
|
|
$existing = $this->getWorkerScheduleOverrideByDate($worker_id, $date);
|
|
if ($existing) {
|
|
$this->db->where('worker_id', $worker_id);
|
|
$this->db->where('date', $date);
|
|
$this->db->update('worker_schedule_overrides', $data);
|
|
} else {
|
|
$data['worker_id'] = $worker_id;
|
|
$data['date'] = $date;
|
|
$this->db->insert('worker_schedule_overrides', $data);
|
|
}
|
|
}
|
|
|
|
public function deleteWorkerScheduleOverrideByDate($worker_id, $date) {
|
|
$this->load->database();
|
|
$this->db->where('worker_id', $worker_id);
|
|
$this->db->where('date', $date);
|
|
$this->db->delete('worker_schedule_overrides');
|
|
}
|
|
|
|
public function getWorkerScheduleByDay($worker_id, $weekday) {
|
|
$this->db->where('worker_id', $worker_id);
|
|
$this->db->where('weekday', $weekday);
|
|
$query = $this->db->get('worker_schedule');
|
|
return $query->row(); // null if no row
|
|
}
|
|
|
|
public function isEvenWeek($date) {
|
|
return ((int)date('W', strtotime($date)) % 2) === 0;
|
|
}
|
|
|
|
public function isWorkerAvailableThisWeek($worker_id, $selectedDate) {
|
|
$weekday = date('w', strtotime($selectedDate));
|
|
$weekNumber = date('W', strtotime($selectedDate));
|
|
$isOddWeek = $weekNumber % 2 !== 0;
|
|
|
|
$query = $this->db->query("
|
|
SELECT * FROM worker_schedule
|
|
WHERE worker_id = ?
|
|
AND weekday = ?
|
|
AND (
|
|
is_alternate_week = 0
|
|
OR (is_alternate_week = 1 AND " . ($isOddWeek ? "1" : "0") . ")
|
|
)
|
|
", array($worker_id, $weekday));
|
|
|
|
return $query->num_rows() > 0;
|
|
}
|
|
|
|
public function getBookingsByDateRange($startDate, $endDate, $workerId = null) {
|
|
$this->load->database();
|
|
|
|
$sql = 'SELECT b.*, w.worker_name, w.worker_profile_img
|
|
FROM bookings b
|
|
JOIN workers w ON b.worker_id = w.worker_id
|
|
WHERE b.booking_date BETWEEN ? AND ?';
|
|
$params = array($startDate, $endDate);
|
|
|
|
if ($workerId) {
|
|
$sql .= ' AND b.worker_id = ?';
|
|
$params[] = $workerId;
|
|
}
|
|
|
|
$sql .= ' ORDER BY b.booking_date, b.booking_start_time';
|
|
$query = $this->db->query($sql, $params);
|
|
|
|
if ($query->num_rows() == 0) return array();
|
|
|
|
$resultsArray = array();
|
|
foreach ($query->result() as $row) {
|
|
$resultTmp = (array)$row;
|
|
$resultTmp['services'] = array();
|
|
$resultTmp['service_names_joined'] = '';
|
|
|
|
if (!empty($resultTmp['service_ids'])) {
|
|
$servicesArr = unserialize($resultTmp['service_ids']);
|
|
if (is_array($servicesArr)) {
|
|
$names = array();
|
|
foreach ($servicesArr as $sid) {
|
|
$svc = $this->getServiceById($sid);
|
|
if (is_object($svc)) {
|
|
$resultTmp['services'][] = (object)array(
|
|
'service_name' => $svc->service_name_hu,
|
|
'service_price' => $svc->service_price,
|
|
'service_time' => $svc->service_time
|
|
);
|
|
$names[] = $svc->service_name_hu;
|
|
}
|
|
}
|
|
$resultTmp['service_names_joined'] = implode(', ', $names);
|
|
}
|
|
}
|
|
|
|
$resultsArray[] = (object)$resultTmp;
|
|
}
|
|
|
|
return $resultsArray;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
?>
|