outlet = new Outlet(); $this->outletTax = new OutletTax(); $this->outletImage = new OutletImage(); $this->outletOperatingDays = new OutletOperatingDay(); $this->outletOperatingHours = new OutletOperatingHour(); $this->outletOperatingHoursExceptions = new OutletOperatingHoursException(); $this->settingTax = new SettingTax(); $this->db = \Config\Database::connect(); $this->outlet_menu = new OutletMenus(); $this->menu_items = new MenuItems(); $this->user = new User(); } public function index() { $user_id = $this->request->getVar('user_id'); if(!empty($user_id)){ $user = $this->user->find($user_id); if($user['role'] == 'outlet'){ $outlets = $this->outlet->getOperatingHoursWithDaysList($user['outlet_id']); } else { $outlets = $this->outlet->getOperatingHoursWithDaysList(); } } else { return $this->respond (['status' => 400, 'result'=> 'User Id is required.']); } if(empty($outlets)){ return $this->respond(['status' => 400, 'result' => 'No outlet data found.']); } return $this->respond(['status' => 200, 'result' => $outlets]); } public function show($id = null) { $outlet = $this->outlet->getOperatingHoursWithDays($id); if(empty($outlet)){ return $this->respond(['status' => 400, 'result' => 'No outlet data found.']); } return $this->respond(['status' => 200, 'result' => $outlet]); } public function create() { $validationRules = [ 'title' => 'required', 'email' => 'required', 'phone' => 'required', 'address' => 'required', 'state' => 'required', 'postal_code' => 'required', 'country' => 'required', 'latitude' => 'required|numeric', 'longitude' => 'required|numeric', 'password' => 'required|min_length[8]', 'outlet_delivery_coverage' => 'required|numeric', 'outlet_tax' => 'permit_empty', 'outlet_operating_days' => 'permit_empty', 'outlet_operating_hours' => 'permit_empty', 'outlet_operating_hours_exceptions' => 'permit_empty', ]; $validation = $this->validate($validationRules); if (!$validation) { return $this->failValidationErrors($this->validator->getErrors()); } // Start database transaction $this->db->transBegin(); try { $outletData = [ "title" => $this->request->getVar('title'), "email" => $this->request->getVar('email'), "phone" => $this->request->getVar('phone'), "address" => $this->request->getVar('address'), "state" => $this->request->getVar('state'), "postal_code" => $this->request->getVar('postal_code'), "country" => $this->request->getVar('country'), "status" => $this->request->getVar('status'), "latitude" => $this->request->getVar('latitude'), "longitude" => $this->request->getVar('longitude'), "password" => md5(md5($this->request->getVar('password'))), "serve_method" => $this->request->getVar('serve_method'), "delivery_options" => $this->request->getVar('delivery_options'), "outlet_delivery_coverage" => $this->request->getVar('outlet_delivery_coverage'), "order_max_per_hour" => $this->request->getVar('order_max_per_hour'), "item_max_per_hour" => $this->request->getVar('item_max_per_hour'), "menu_item" => json_encode($this->request->getVar('outlet_menu')), ]; $id = $this->outlet->insert($outletData); if (!$id) { throw new \RuntimeException('Failed to insert outlet data'); } $outlet_menu = $this->request->getVar('outlet_menu'); if(!empty($outlet_menu)){ foreach($outlet_menu as $menu){ $outletMenuItem = [ "outlet_id" => $id, "menu_item_id" => $menu, ]; if (!$this->outlet_menu->insert($outletMenuItem)) { throw new \RuntimeException('Failed to insert outlet tax data'); } } } // Process taxes $tax_ids = $this->request->getVar('outlet_tax'); if(!empty($tax_ids)){ foreach ($tax_ids as $tax_id) { $outletTaxData = [ "outlet_id" => $id, "tax_id" => $tax_id, ]; if (!$this->outletTax->insert($outletTaxData)) { throw new \RuntimeException('Failed to insert outlet tax data'); } } } // Process images $images = $this->request->getFiles('outlet_images'); $outlet_images = []; if (!empty($images['outlet_images'])) { foreach ($images['outlet_images'] as $index => $image) { if (empty($image) || !$image->isValid() || $image->hasMoved()) { continue; } try { $result = save_image_with_compression( $image, FCPATH . 'backend/uploads/outlets', FCPATH . 'backend/uploads/outlets_compressed', 900, // width 80 // quality ); $originalFileName = basename($result['original']); $compressedFileName = basename($result['compressed']); if (!$this->outletImage->insert([ "outlet_id" => $id, "image_url" => $originalFileName, "compressed_image_url" => $compressedFileName, "order_index" => $index ])) { @unlink($result['original']); @unlink($result['compressed']); throw new \RuntimeException('Failed to insert outlet image data'); } $outlet_images[$index] = [ 'image_url' => $originalFileName, 'compressed_image_url' => $compressedFileName, ]; } catch (\Exception $e) { log_message('error', 'Failed to upload and compress image: ' . $e->getMessage()); throw $e; } } } // Process operating days $outlet_operating_days = json_decode($this->request->getVar('outlet_operating_days'), true); $operating_days = []; // print_r($this->request->getVar('outlet_operating_days'));exit; if(!empty($outlet_operating_days[0])){ foreach ($outlet_operating_days[0] as $index => $day) { $day_id = $this->outletOperatingDays->insert([ "outlet_id" => $id, "day_of_week" => $index, "is_operated" => $day['is_operated'], ]); if (!$day_id) { throw new \RuntimeException('Failed to insert operating days data'); } $operating_days[$index] = [ 'id' => $day_id, "outlet_id" => $id, "day_of_week" => $index, "is_operated" => $day['is_operated'], ]; } } // Process operating hours $outlet_operating_hours = json_decode($this->request->getVar('outlet_operating_hours'), true); $operating_hours = []; if(!empty($outlet_operating_hours[0])){ foreach ($outlet_operating_hours[0] as $index => $hours) { foreach ($hours as $hour) { $hour_id = $this->outletOperatingHours->insert([ "outlet_id" => $id, "day_of_week" => $index, "start_time" => $hour['start_time'], "end_time" => $hour['end_time'], ]); if (!$hour_id) { throw new \RuntimeException('Failed to insert operating hours data'); } $operating_hours[$index][] = [ 'id' => $hour_id, "outlet_id" => $id, "day_of_week" => $index, "start_time" => $hour['start_time'], "end_time" => $hour['end_time'], ]; } } } // Process operating hours exceptions $outlet_operating_hours_exceptions = json_decode($this->request->getVar('outlet_operating_hours_exceptions'), true); $operating_hours_exceptions = []; if (!empty($outlet_operating_hours_exceptions)) { foreach ($outlet_operating_hours_exceptions as $index => $exception) { $hours_exception_id = $this->outletOperatingHoursExceptions->insert([ "outlet_id" => $id, "date" => $exception['date'], "start_time" => $exception['start_time'], "end_time" => $exception['end_time'], "notes" => $exception['notes'], ]); if (!$hours_exception_id) { throw new \RuntimeException('Failed to insert operating hours exceptions data'); } $operating_hours_exceptions[$index] = [ 'id' => $hours_exception_id, "outlet_id" => $id, "date" => $exception['date'], "start_time" => $exception['start_time'], "end_time" => $exception['end_time'], "notes" => $exception['notes'], ]; } } // Commit transaction if everything succeeded $this->db->transCommit(); $result = [ 'id' => $id, 'title' => $this->request->getVar('title'), 'email' => $this->request->getVar('email'), 'phone' => $this->request->getVar('phone'), 'address' => $this->request->getVar('address'), 'state' => $this->request->getVar('state'), 'postal_code' => $this->request->getVar('postal_code'), 'country' => $this->request->getVar('country'), 'status' => $this->request->getVar('status'), 'latitude' => $this->request->getVar('latitude'), 'longitude' => $this->request->getVar('longitude'), 'password' => md5(md5($this->request->getVar('password'))), 'serve_method' => $this->request->getVar('serve_method'), 'delivery_options' => $this->request->getVar('delivery_options'), 'outlet_delivery_coverage' => $this->request->getVar('outlet_delivery_coverage'), 'order_max_per_hour' => $this->request->getVar('order_max_per_hour'), 'item_max_per_hour' => $this->request->getVar('item_max_per_hour'), 'outlet_tax' => $tax_ids, 'outlet_images' => $outlet_images, 'outlet_operating_days' => $operating_days, 'outlet_operating_hours' => $operating_hours, 'outlet_operating_hours_exceptions' => $operating_hours_exceptions ?? [], 'menu_item' => $this->request->getVar('outlet_menu'), ]; return $this->respond(['status' => 200, 'result' => $result]); } catch (\Exception $e) { // Rollback transaction on any error $this->db->transRollback(); // Clean up any uploaded files if transaction fails if (!empty($outlet_images)) { $uploadPath = $_SERVER['DOCUMENT_ROOT'] . '/backend/uploads/outlets/'; foreach ($outlet_images as $image) { @unlink($uploadPath . $image); } } log_message('error', 'Outlet creation failed: ' . $e->getMessage()); return $this->failServerError('Failed to create outlet: ' . $e->getMessage()); } } public function update($id = null) { $outlet = $this->outlet->find($id); if (empty($outlet)) { return $this->respond(['status' => 400, 'result' => 'No outlet data found.']); } $validationRules = [ 'title' => 'required', 'email' => 'required', 'phone' => 'required', 'address' => 'required', 'state' => 'required', 'postal_code' => 'required', 'country' => 'required', 'latitude' => 'required|numeric', 'longitude' => 'required|numeric', 'outlet_delivery_coverage' => 'required|numeric', 'outlet_tax' => 'permit_empty', 'outlet_operating_days' => 'permit_empty', 'outlet_operating_hours' => 'permit_empty', 'outlet_operating_hours_exceptions' => 'permit_empty', 'outlet_images' => 'permit_empty' ]; // Get all input data $input = $this->request->getVar(); // Decode only the JSON fields $jsonFields = [ 'outlet_tax', 'outlet_operating_days', 'outlet_operating_hours', 'outlet_operating_hours_exceptions' ]; foreach ($jsonFields as $field) { if (isset($input[$field]) && is_string($input[$field])) { $input[$field] = json_decode($input[$field], true); } } if (!$this->validateData($input, $validationRules)) { return $this->failValidationErrors($this->validator->getErrors()); } $outlet = $this->outlet->find($id); if(empty($outlet)){ return $this->respond(['status' => 400, 'result' => 'No outlet data found.']); } // Prepare outlet data $outlet_data = ['id' => $id]; $fields = [ 'title', 'email', 'phone', 'address', 'state', 'postal_code', 'country', 'status', 'latitude', 'longitude', 'serve_method', 'delivery_options', 'outlet_delivery_coverage', 'order_max_per_hour', 'item_max_per_hour' ]; foreach ($fields as $field) { if (isset($input[$field])) { $outlet_data[$field] = $input[$field]; } } if (!empty($input['password']) && $input['password'] != 'null' && $input['password'] != '' && $input['password'] != 'undefined') { $outlet_data['password'] = md5(md5($input['password'])); } else { $outlet_data['password'] = $outlet['password']; } // print_r($outlet_data);exit; // Start transaction $this->db->transBegin(); try { // Update main outlet data $response = $this->outlet->save($outlet_data); if (!$response) { throw new \Exception('Failed to update outlet'); } // Handle outlet taxes if (isset($input['outlet_tax']) && is_array($input['outlet_tax'])) { $existingTaxes = $this->outletTax->where('outlet_id', $id) ->where('deleted_at', null) ->findAll(); $existingTaxIds = array_column($existingTaxes, 'tax_id', 'id'); $updatedTaxIds = []; foreach ($input['outlet_tax'] as $tax) { $taxData = [ 'outlet_id' => $id, 'tax_id' => $tax['tax_id'], 'updated_at' => date('Y-m-d H:i:s') ]; $taxId = array_search($tax['tax_id'], $existingTaxIds); if ($taxId !== false) { $taxData['id'] = $taxId; $this->outletTax->save($taxData); $updatedTaxIds[] = $taxId; } else { $taxData['created_at'] = date('Y-m-d H:i:s'); $newId = $this->outletTax->insert($taxData); $updatedTaxIds[] = $newId; } } // Soft delete removed taxes foreach ($existingTaxes as $existingTax) { if (!in_array($existingTax['id'], $updatedTaxIds)) { $this->outletTax->where('id', $existingTax['id']) ->set(['deleted_at' => date('Y-m-d H:i:s')]) ->update(); } } } $images = $this->request->getFiles(); if (!empty($images['outlet_images'])) { $images = $images['outlet_images']; } $existing_image = $this->request->getVar("existing_image"); if (!empty($images)) { $exist_images = $this->outletImage->where('outlet_id', $id)->findAll(); $exist_images_group = array_column($exist_images, 'id'); foreach ($images as $image_file) { if ($image_file && $image_file->isValid() && !$image_file->hasMoved()) { $result = save_image_with_compression( $image_file, FCPATH . 'backend/uploads/outlets', FCPATH . 'backend/uploads/outlets_compressed', 900, // width 80 // quality ); $originalFileName = basename($result['original']); $compressedFileName = basename($result['compressed']); if (!$this->outletImage->insert([ 'outlet_id' => $id, 'image_url' => $originalFileName, 'compressed_image_url' => $compressedFileName ])) { throw new \Exception('Failed to update image!'); } } } if (!empty($existing_image)) { foreach ($exist_images_group as $image_id) { if (!in_array($image_id, $existing_image)) { $this->outletImage->delete($image_id); } } } } // Handle operating days if (isset($input['outlet_operating_days']) && is_array($input['outlet_operating_days'])) { $operatingDays = $input['outlet_operating_days'][0]; $existingDays = $this->outletOperatingDays ->where('outlet_id', $id) ->findAll(); $existingDayMap = []; foreach ($existingDays as $day) { $existingDayMap[$day['day_of_week']] = $day; } $updatedDays = []; foreach ($operatingDays as $dayName => $dayData) { $dayOfWeek = ucfirst(strtolower($dayName)); $dayRecord = [ 'outlet_id' => $id, 'day_of_week' => $dayOfWeek, 'is_operated' => $dayData['is_operated'], 'updated_at' => date('Y-m-d H:i:s') ]; if (isset($existingDayMap[$dayOfWeek])) { $dayRecord['id'] = $existingDayMap[$dayOfWeek]['id']; $this->outletOperatingDays->save($dayRecord); $updatedDays[] = $dayOfWeek; } else { $dayRecord['created_at'] = date('Y-m-d H:i:s'); $this->outletOperatingDays->insert($dayRecord); $updatedDays[] = $dayOfWeek; } } // Soft delete removed days foreach ($existingDays as $day) { if (!in_array($day['day_of_week'], $updatedDays)) { $this->outletOperatingDays->where('id', $day['id']) ->set(['deleted_at' => date('Y-m-d H:i:s')]) ->update(); } } } // Handle operating hours if (isset($input['outlet_operating_hours']) && is_array($input['outlet_operating_hours'])) { $operatingHours = $input['outlet_operating_hours'][0]; $existingHours = $this->outletOperatingHours ->where('outlet_id', $id) ->findAll(); $existingHoursByDay = []; foreach ($existingHours as $hour) { $existingHoursByDay[$hour['day_of_week']][] = $hour; } foreach ($operatingHours as $dayName => $timeSlots) { $dayOfWeek = ucfirst(strtolower($dayName)); $updatedHourIds = []; if (!empty($timeSlots)) { foreach ($timeSlots as $slot) { $hourRecord = [ 'outlet_id' => $id, 'day_of_week' => $dayOfWeek, 'start_time' => $slot['start_time'], 'end_time' => $slot['end_time'], 'updated_at' => date('Y-m-d H:i:s') ]; $found = false; if (isset($existingHoursByDay[$dayOfWeek])) { foreach ($existingHoursByDay[$dayOfWeek] as $existingHour) { if ($existingHour['start_time'] == $slot['start_time'] && $existingHour['end_time'] == $slot['end_time']) { $hourRecord['id'] = $existingHour['id']; $this->outletOperatingHours->save($hourRecord); $updatedHourIds[] = $existingHour['id']; $found = true; break; } } } if (!$found) { $hourRecord['created_at'] = date('Y-m-d H:i:s'); $newId = $this->outletOperatingHours->insert($hourRecord); $updatedHourIds[] = $newId; } } } // Soft delete removed hours if (isset($existingHoursByDay[$dayOfWeek])) { foreach ($existingHoursByDay[$dayOfWeek] as $existingHour) { if (!in_array($existingHour['id'], $updatedHourIds)) { $this->outletOperatingHours ->where('id', $existingHour['id']) ->set(['deleted_at' => date('Y-m-d H:i:s')]) ->update(); } } } } } // Handle outlet menu items // print_r($input);exit; // print_r($input['outlet_menu']);exit; if (isset($input['outlet_menu']) && is_array($input['outlet_menu'])) { // Filter out empty values (including 0 and '') $filteredMenuIds = array_filter($input['outlet_menu'], function($menuId) { return $menuId !== '' && $menuId !== null && $menuId !== 0; }); if (!empty($filteredMenuIds)) { // Fetch existing menu items $existingMenu = $this->outlet_menu ->where('outlet_id', $id) ->where('deleted_at', null) ->findAll(); $existingMenuIds = array_column($existingMenu, 'menu_item_id', 'id'); $updatedMenuIds = []; foreach ($filteredMenuIds as $menuId) { if (in_array($menuId, $existingMenuIds)) { $updatedMenuIds[] = $menuId; } else { $newId = $this->outlet_menu->insert([ 'outlet_id' => $id, 'menu_item_id' => $menuId, ]); $updatedMenuIds[] = $newId; } } // Delete unused menu items foreach ($existingMenu as $existing) { if (!in_array($existing['menu_item_id'], $updatedMenuIds)) { $this->outlet_menu ->where('menu_item_id', $existing['menu_item_id']) ->where('outlet_id', $id) ->where('deleted_at', null) ->delete(); } } } else { // Delete ALL menu items if array is empty after filtering $this->outlet_menu ->where('outlet_id', $id) ->where('deleted_at', null) ->delete(); } } // Handle operating hours exceptions if (isset($input['outlet_operating_hours_exceptions']) && is_array($input['outlet_operating_hours_exceptions'])) { $existingExceptions = $this->outletOperatingHoursExceptions ->where('outlet_id', $id) ->where('deleted_at', null) ->findAll(); $existingExceptionKeys = []; foreach ($existingExceptions as $ex) { $key = $ex['date'].'|'.$ex['start_time'].'|'.$ex['end_time']; $existingExceptionKeys[$key] = $ex['id']; } $updatedExceptionIds = []; foreach ($input['outlet_operating_hours_exceptions'] as $exception) { $exceptionData = [ 'outlet_id' => $id, 'date' => $exception['date'], 'start_time' => $exception['start_time'], 'end_time' => $exception['end_time'], 'notes' => $exception['notes'] ?? null, 'updated_at' => date('Y-m-d H:i:s') ]; $compositeKey = $exception['date'].'|'.$exception['start_time'].'|'.$exception['end_time']; if (isset($existingExceptionKeys[$compositeKey])) { $exceptionData['id'] = $existingExceptionKeys[$compositeKey]; $this->outletOperatingHoursExceptions->save($exceptionData); $updatedExceptionIds[] = $exceptionData['id']; } else { $exceptionData['created_at'] = date('Y-m-d H:i:s'); $newId = $this->outletOperatingHoursExceptions->insert($exceptionData); $updatedExceptionIds[] = $newId; } } // Soft delete removed exceptions foreach ($existingExceptions as $existingException) { if (!in_array($existingException['id'], $updatedExceptionIds)) { $this->outletOperatingHoursExceptions ->where('id', $existingException['id']) ->set(['deleted_at' => date('Y-m-d H:i:s')]) ->update(); } } } // Commit transaction $this->db->transCommit(); // Prepare response $result = [ 'outlet' => array_merge($outlet_data, ['id' => $id]), 'outlet_tax' => $input['outlet_tax'] ?? [], 'outlet_images' => $images['outlet_images'] ?? [], 'outlet_operating_days' => $input['outlet_operating_days'] ?? [], 'outlet_operating_hours' => $input['outlet_operating_hours'] ?? [], 'outlet_operating_hours_exceptions' => $input['outlet_operating_hours_exceptions'] ?? [] ]; return $this->respond(['status' => 200, 'result' => $result]); } catch (\Exception $e) { $this->db->transRollback(); return $this->respond(['status' => 400, 'message' => 'Failed to update outlet: ' . $e->getMessage()]); } } public function delete($id = null) { $outlet = $this->outlet->find($id); if(empty($outlet)){ return $this->respond(['status' => 400, 'result' => 'No outlet data found.']); } $this->outlet->delete($id); $this->outletTax->where('outlet_id', $id)->delete(); $this->outletOperatingDays->where('outlet_id', $id)->delete(); $this->outletOperatingHours->where('outlet_id', $id)->delete(); $this->outletOperatingHoursExceptions->where('outlet_id', $id)->delete(); return $this->respond(['status' => 200, 'result' => 'Outlet deleted successfully.']); } public function updatePassword($id = null) { $outlet = $this->outlet->find($id); if(empty($outlet)){ return $this->respond(['status' => 400, 'result' => 'No outlet data found.']); } $password = $this->request->getVar('password'); $this->outlet->update($id, ['password' => md5(md5($password))]); return $this->respond(['status' => 200, 'result' => 'Outlet password updated successfully.']); } public function addBulk() { $rules = [ 'outlet_ids' => 'required|is_array', 'menu_item_ids' => 'required|is_array', ]; if (!$this->validate($rules)) { return $this->failValidationErrors($this->validator->getErrors()); } $outletIds = $this->request->getVar('outlet_ids'); $menuItemIds = $this->request->getVar('menu_item_ids'); $insertData = []; $insertCount = 0; $restoreCount = 0; foreach ($outletIds as $outletId) { foreach ($menuItemIds as $menuItemId) { $exists = $this->outlet_menu ->withDeleted() // include soft deleted to detect them ->where('outlet_id', $outletId) ->where('menu_item_id', $menuItemId) ->first(); if (!$exists) { $insertData[] = [ 'outlet_id' => $outletId, 'menu_item_id' => $menuItemId, ]; $insertCount++; } elseif (!empty($exists['deleted_at'])) { // Restore if it was soft deleted $this->outlet_menu ->update($exists['id'], ['deleted_at' => null]); $restoreCount++; } } } if (!empty($insertData)) { $this->outlet_menu->insertBatch($insertData); } return $this->respond([ 'status' => 200, 'message' => "Menu items added to outlets successfully.", 'added' => $insertCount, 'restored' => $restoreCount ]); } public function deleteBulk() { $rules = [ 'outlet_ids' => 'required|is_array', 'menu_item_ids' => 'required|is_array', ]; if (!$this->validate($rules)) { return $this->failValidationErrors($this->validator->getErrors()); } $outletIds = $this->request->getVar('outlet_ids'); $menuItemIds = $this->request->getVar('menu_item_ids'); $deleteCount = 0; foreach ($outletIds as $outletId) { // Soft delete only the specified menu items for each outlet $deleteCount += $this->outlet_menu ->where('outlet_id', $outletId) ->whereIn('menu_item_id', $menuItemIds) ->delete(); // soft delete because model usesSoftDeletes = true } return $this->respond([ 'status' => 200, 'message' => "Outlet menus deleted successfully.", 'deleted' => $deleteCount, 'outlets_processed' => count($outletIds), 'menu_items_processed' => count($menuItemIds) ]); } }