cart_items = new CartItems(); $this->variations = new MenuItemVariations(); $this->options = new CartItemOptions(); $this->carts = new Carts(); $this->menu_items = new MenuItems(); $this->menu_images = new MenuImages(); $this->outlet_menus = new OutletMenus(); $this->outlet_tax = new OutletTax(); $this->setting_tax = new SettingTax(); $this->cart_item_options = new CartItemOptions(); $this->promo_service = $promoService; $this->promo_codes = new PromoCode(); $this->customer_voucher_list = new CustomerVoucherList(); $this->outlet = new Outlet(); $this->store_discount = new StoreDiscount(); $this->customer = new Customer(); $this->pwp = new PwpModel(); $this->setting_delivery_fee = new DeliverySettingModel(); helper('order_helper'); } private function getOriginalItemPrice($menu_item_id) { $menu_item = $this->menu_items->find($menu_item_id); return $menu_item ? $menu_item['price'] : 0; } private function checkItemPwpDiscount($cart_id) { $cart = $this->carts->find($cart_id); $cart_items = $this->cart_items->where('cart_id', $cart_id)->findAll(); // print_r($cart_items);exit; $pwp_promos = $this->pwp->where('deleted_at', null)->orderBy('order_index', 'ASC')->findAll(); $total_qualify = 0; foreach ($pwp_promos as $promo) { if($promo['mode'] == 'selected_item'){ $requiredIds = explode(',', $promo['pwp_item_id']); foreach($cart_items as $item){ if(in_array($item['menu_item_id'], $requiredIds)){ // echo(123);exit; if($promo['amount_type'] == 'amount'){ $total_qualify+=($item['quantity'] * $item['unit_price']); }else{ $total_qualify+=$item['quantity']; } } } }else{ if($promo['amount_type'] == 'amount'){ $total_qualify+= $cart['grand_total']; }else{ $total_qualify+= $cart['item_count']; } } // echo($total_qualify);exit; if($total_qualify >= $promo['amount']){ return [ 'has_discount' => true, 'pwp_item' => explode(',', $promo['selected_item']), ]; } } return ['has_discount' => false]; } public function calculateCartTotals($cart_id, $outlet_id, $selected_date = null, $selected_time = null, $latitude = null, $longitude = null, $order_type = null, $address = null, $promo_or_voucher = null) { $cart = $this->carts->find($cart_id); if ($selected_date == null && $selected_time == null) { $selected_date = $cart['selected_date']; $selected_time = $cart['selected_time']; } if ($order_type == null) { $order_type = $cart['order_type']; } if ($address == null) { $address = $cart['address']; } if ($latitude == null && $longitude == null) { $latitude = $cart['latitude']; $longitude = $cart['longitude']; } $cart_items = $this->cart_items->where('cart_id', $cart_id)->findAll(); $customer_id = $cart['customer_id']; $customer = $this->customer->where('id', $customer_id)->first(); $customer_tier = $customer['customer_tier_id'] ?? 0; $subtotal = $subtotal_without_options = 0; $total_discount = $promo_discount_amount = 0; $total_tax = 0; $invalid_items = []; $promo_code_id = $cart['promo_code_id']; $free_item_list = []; $promo_code = ''; $voucher_code = ''; $packaging_charge = 0; $array_discount = []; $store_discount = $this->store_discount->where("FIND_IN_SET($outlet_id, outlet_list)")->where("FIND_IN_SET($customer_tier, tier_id_list)")->findAll(); // print_r($cart_items);exit; foreach ($cart_items as $item) { $is_valid = true; if ($outlet_id) { $menu_available = $this->outlet_menus->where([ 'menu_item_id' => $item['menu_item_id'], 'outlet_id' => $outlet_id ])->first(); if (!$menu_available) { $is_valid = false; $invalid_items[] = $item['id']; $this->options->where('cart_item_id', $item['id'])->delete(); continue; } } $unit_price = $item['unit_price']; $title = $item['title']; $variation_price = 0; $packaging_price = 0; if (!empty($item['variation_id']) && $item['variation_id'] > 0) { $menu_item = $this->menu_items->find($item['menu_item_id']); $variation = $this->variations->find($item['variation_id']); if ($variation) { $unit_price = $variation['price']; $packaging_price = $menu_item['packaging_price'] ?? 0; $packaging_charge = $packaging_price * $item['quantity']; } } else { $menu_item = $this->menu_items->find($item['menu_item_id']); if ($menu_item) { $unit_price = $item['is_pwp'] == true ? $menu_item['pwp_price'] : $menu_item['price']; $title = $menu_item['title']; $packaging_price = $menu_item['packaging_price'] ?? 0; $packaging_charge = $packaging_price * $item['quantity']; } } if(is_array($store_discount)){ foreach($store_discount as $discount){ $menu_item_list = explode(',', $discount['menu_item_list']); if(in_array($item['menu_item_id'], $menu_item_list)){ if($discount['discount_type'] == 'percentage'){ $total_discount += ($unit_price * $item['quantity']) * ($discount['discount_value'] / 100); if(!array_key_exists($discount['id'], $array_discount)){ $array_discount[$discount['id']] = [ 'discount_name' => $discount['discount_name'], 'discount_type' => $discount['discount_type'], 'discount_value' => $discount['discount_value'], 'discount_amount' => number_format_no_round(($unit_price * $item['quantity']) * ($discount['discount_value'] / 100)) ]; }else{ $array_discount[$discount['id']]['discount_amount'] += number_format_no_round(($unit_price * $item['quantity']) * ($discount['discount_value'] / 100)); } }else{ if(!array_key_exists($discount['id'], $array_discount)){ $total_discount = $discount['discount_value']; $array_discount[$discount['id']] = [ 'discount_name' => $discount['discount_name'], 'discount_type' => $discount['discount_type'], 'discount_value' => $discount['discount_value'], 'discount_amount' => number_format_no_round(($unit_price * $item['quantity']) * ($discount['discount_value'] / 100)) ]; } } } } } $item_subtotal = ($unit_price) * $item['quantity']; $item_subtotal_without_options = $item_subtotal; $options = $this->options->where('cart_item_id', $item['id'])->findAll(); $options_total = array_sum(array_column($options, 'price_adjustment')); $item_subtotal += $options_total; if (($item['unit_price'] != $unit_price || $item['title'] != $title) && (($item['is_free_item'] == false || $item['is_free_item'] == '1') && $item['is_pwp'] == false)) { $this->cart_items->update($item['id'], [ 'unit_price' => $unit_price, 'title' => $title, 'line_subtotal' => $item_subtotal ]); } if ($item['is_free_item'] == true || $item['is_free_item'] == '1') { $item_subtotal = 0; } if($item['is_pwp'] == true && ($item['unit_price'] != $unit_price || $item['title'] != $title)){ // $item_subtotal = $item['pwp_price'] * $item['quantity']; $this->cart_items->update($item['id'], [ 'unit_price' => $unit_price, 'title' => $item['title'], 'line_subtotal' => $item_subtotal ]); } if ($is_valid && $item['is_free_item'] != true && $item['is_free_item'] != '1') { $subtotal += $item_subtotal; $subtotal_without_options += $item_subtotal_without_options; } } if($subtotal < $total_discount){ $total_discount = $subtotal; } if (!empty($invalid_items)) { $this->cart_items->whereIn('id', $invalid_items)->delete(); $cart_items = $this->cart_items->where('cart_id', $cart_id)->findAll(); } // Get cart items with options (after potential deletions) $final_cart_items = $this->cart_items ->where('cart_id', $cart_id) ->where('deleted_at', null) ->findAll(); foreach ($final_cart_items as &$item) { $item['variation'] = $this->variations->find($item['variation_id']); $item['options'] = $this->options ->where('cart_item_id', $item['id']) ->where('deleted_at', null) ->findAll(); if ($item['variation_id'] > 0) { $item['image'] = $item['variation']['images']; } else { $image = $this->menu_images->where('menu_item_id', $item['menu_item_id'])->orderBy('order_index', 'ASC')->first(); if ($image) { $item['image'] = 'https://icom.ipsgroup.com.my/backend/uploads/menu_images/'.$image['image_url']; } else { $item['image'] = null; } } } //check store discount // $store_discount = $this->store_discount->where("FIND_IN_SET($outlet_id, outlet_list)")->where("FIND_IN_SET($order_type, order_type)")->findAll(); // print_r($final_cart_items);exit; $quotation_id = null; $delivery_fee = 0; $delivery_service = null; if ($order_type == 'delivery' && $latitude && $longitude && $address) { $outlet_data = $this->outlet->where('id', $outlet_id)->first(); $delivery_options = explode(',', $outlet_data['delivery_options'] ?? ''); // Lalamove if (in_array('Lalamove', $delivery_options)) { try { $lalamove = new Lalamove(); $delivery_response = $lalamove->requestQuotation( $selected_date, $selected_time, $subtotal, $outlet_id, $latitude, $longitude, $address ); if (isset($delivery_response['data'])) { $quotation_id = $delivery_response['data']['quotationId']; $delivery_fee = $delivery_response['data']['priceBreakdown']['total']; $delivery_service = 'Lalamove'; } } catch (Exception $e) { log_message('error', 'Lalamove quotation failed: ' . $e->getMessage()); } } // Grab if (!$quotation_id && in_array('Grab Express', $delivery_options)) { try { $grab = new \App\Libraries\Grab(); $delivery_response = $grab->requestQuotation( $selected_date, $selected_time, $subtotal, $outlet_id, $latitude, $longitude, $address ); if (!empty($delivery_response['quotes'])) { $quotation_id = time(); $delivery_fee = $delivery_response['quotes'][0]['amount'] ?? 0; $delivery_service = 'Grab'; } } catch (Exception $e) { log_message('error', 'Grab quotation failed: ' . $e->getMessage()); } } // Manual fallback if (!$quotation_id) { $distance = $this->calculateDistance($outlet_id, $latitude, $longitude); $delivery_fee = $this->calculateDeliveryFee($distance); } } if ($cart['promo_code_id'] > 0 || $cart['customer_voucher_list_id'] > 0) { $promo_setting_id = 0; //check promo if ($cart['promo_code_id'] > 0) { $promo_data = $this->promo_codes->find($cart['promo_code_id']); $promo_code = $promo_data['code']; $promo_setting_id = $promo_data['promo_setting_id']; } else if ($cart['customer_voucher_list_id'] > 0) { $voucher_data = $this->customer_voucher_list->where('id', $cart['customer_voucher_list_id'])->first(); $voucher_code = $voucher_data['voucher_code']; $promo_setting_id = $voucher_data['promo_setting_id']; } // print_r($final_cart_items);exit; $promo = $this->promo_service->checkPromoLogic($promo_setting_id, $final_cart_items, number_format($subtotal_without_options, 2), $cart['customer_id'], $outlet_id, $order_type, $delivery_fee); if (isset($promo['status']) && $promo['status'] == 400) { //remove from cart $result = $this->resetVoucher($cart_id); // print_r($promo); // exit; return $promo; } if (isset($promo['status']) && $promo['status'] == 200) { // print_r($promo);exit; if ($promo['delivery_fee'] > 0) { $promo_discount_amount += $promo['delivery_fee']; } if ($promo['promo_discount_total'] > 0) { $promo_discount_amount += $promo['promo_discount_total']; } $free_item_list = $promo['free_item_list']; } } $tax_detail = []; $grand_total = $subtotal + $packaging_charge - $total_discount - $promo_discount_amount; if($grand_total < 0){ $grand_total = 0; } $tax_array = $this->setting_tax->where("FIND_IN_SET($outlet_id, outlet_id)")->where("FIND_IN_SET('" . $order_type . "', order_type)")->findAll(); foreach ($tax_array as $tax) { $add_tax = $grand_total * ($tax['tax_rate'] / 100); $total_tax += $add_tax; $tax_detail[] = [ 'tax_type' => $tax['tax_type'], 'tax_rate' => $tax['tax_rate'], 'tax_amount' => number_format_no_round($add_tax) ]; } //add tax // echo $total_tax;exit; $grand_total += $total_tax; $grand_total = number_format_no_round($grand_total); // echo($grand_total);exit; $rounding_amount = calculateRoundingAmount($grand_total); // echo $rounding_amount;exit; $grand_total = $grand_total + $rounding_amount; // echo $grand_total;exit; //calculate rounding amount //call delivery service api if delivery // print_r($final_cart_items);exit; // Update cart totals in database $this->carts->update($cart_id, [ 'item_count' => count($cart_items), 'subtotal_amount' => number_format($subtotal, 2), 'discount_amount' => number_format($total_discount, 2), 'tax_amount' => number_format($total_tax, 2), 'grand_total' => number_format($grand_total, 2), 'rounding_amount' => number_format($rounding_amount, 2), 'promo_discount_amount' => $promo_or_voucher == 'promo' ? number_format($promo_discount_amount, 2) : 0.00, 'voucher_discount_amount' => $promo_or_voucher == 'voucher' ? number_format($promo_discount_amount, 2) : 0.00, 'delivery_fee' => number_format($delivery_fee, 2), 'selected_date' => $selected_date, 'selected_time' => $selected_time, 'latitude' => $latitude, 'longitude' => $longitude, 'order_type' => $order_type, 'address' => $address, 'lalamove_quot_id' => $delivery_service === 'Lalamove' ? $quotation_id : null, 'grab_quot_id' => $delivery_service === 'Grab' ? $quotation_id : null ]); // print_r($final_cart_items);exit; //check pwp $pwp_menu = []; $pwp_discount = $this->checkItemPwpDiscount($cart_id); // print_r($final_cart_items);exit; if($pwp_discount['has_discount']){ $pwp_items = $pwp_discount['pwp_item']; foreach($pwp_items as $pwp_item){ $pwp_menu_item = $this->menu_items->where('id', $pwp_item)->first(); if(isset($pwp_menu_item['pwp_price'])){ if($pwp_menu_item['pwp_price'] > 0){ $pwp_menu[] = $pwp_menu_item; } } } } // print_r($pwp_menu);exit; // Return the complete structure return [ 'status' => 200, 'message' => 'Cart retrieved successfully.', 'data' => [ 'id' => $cart['id'], 'customer_id' => $cart['customer_id'], 'outlet_id' => $cart['outlet_id'], 'status' => $cart['status'], 'order_summary' => [ 'item_count' => count($final_cart_items), 'subtotal_amount' => number_format($subtotal, 2), 'discount_amount' => number_format($total_discount, 2), 'tax_amount' => number_format($total_tax, 2), 'delivery_fee' => number_format($delivery_fee, 2), 'packaging_charge' => number_format($packaging_charge, 2), // 'packaging_charge' => "123", 'grand_total' => number_format($grand_total, 2), 'store_discount' => $array_discount, 'store_discount_amount' => number_format($total_discount, 2), 'grand_total_without_rounding' => number_format($grand_total - $rounding_amount, 2), 'rounding_amount' => number_format($rounding_amount, 2), 'promo_code_id' => $promo_code_id ?? 0, 'promo_discount_amount' => $promo_or_voucher == 'promo' ? number_format($promo_discount_amount, 2) : 0.00, 'promo_code' => $promo_code, 'customer_voucher_list_id' => $cart['customer_voucher_list_id'], 'voucher_discount_amount' => $promo_or_voucher == 'voucher' ? number_format($promo_discount_amount, 2) : 0.00, 'voucher_code' => $voucher_code, 'selected_date' => $selected_date, 'selected_time' => $selected_time, 'latitude' => $latitude, 'longitude' => $longitude, 'order_type' => $order_type, 'address' => $address ], 'tax_detail' => $tax_detail, 'invalid_items' => $invalid_items, 'items' => $final_cart_items, 'free_item_list' => $free_item_list, 'pwp_menu' => $pwp_menu ] ]; } public function resetVoucher($cart_id) { // Reset promo and voucher fields $this->carts->update($cart_id, [ 'promo_code_id' => 0, 'promo_discount_amount' => 0, 'customer_voucher_list_id' => 0, 'voucher_discount_amount' => 0 ]); // Get free items from cart $freeItems = $this->cart_items ->select('id') ->where('cart_id', $cart_id) ->where('is_free_item', 1) ->findAll(); if (!empty($freeItems)) { // Collect all item IDs $itemIds = array_column($freeItems, 'id'); // Delete all related item options in one query $this->cart_item_options->whereIn('cart_item_id', $itemIds)->delete(); // Delete all free items in one query $this->cart_items->whereIn('id', $itemIds)->delete(); } } /** * Calculate distance from outlet to customer using Google Distance Matrix API * @param int $outlet_id * @param float $latitude * @param float $longitude * @return float Distance in kilometers */ public function calculateDistance($outlet_id, $latitude, $longitude) { try { // Validate input parameters if (empty($outlet_id) || !is_numeric($outlet_id)) { log_message('error', 'CalculateService: Invalid outlet ID provided: ' . $outlet_id); return 0; } if (!is_numeric($latitude) || !is_numeric($longitude)) { log_message('error', 'CalculateService: Invalid coordinates provided - lat: ' . $latitude . ', lng: ' . $longitude); return 0; } // Validate coordinate ranges if ($latitude < -90 || $latitude > 90 || $longitude < -180 || $longitude > 180) { log_message('error', 'CalculateService: Coordinates out of valid range - lat: ' . $latitude . ', lng: ' . $longitude); return 0; } // Get outlet coordinates $outlet = $this->outlet->find($outlet_id); if (!$outlet || !isset($outlet['latitude']) || !isset($outlet['longitude'])) { log_message('error', 'CalculateService: Outlet not found or missing coordinates for outlet ID: ' . $outlet_id); return 0; } $outlet_lat = $outlet['latitude']; $outlet_lng = $outlet['longitude']; // Check if we have valid outlet coordinates if (!is_numeric($outlet_lat) || !is_numeric($outlet_lng)) { log_message('error', 'CalculateService: Invalid outlet coordinates - lat: ' . $outlet_lat . ', lng: ' . $outlet_lng); return 0; } // Check if Google API key is configured if (empty(GOOGLE_DISTANCE_MATRIX_API_KEY) || GOOGLE_DISTANCE_MATRIX_API_KEY === 'YOUR_GOOGLE_API_KEY_HERE') { log_message('error', 'CalculateService: Google Distance Matrix API key not configured'); return 0; } // Google Distance Matrix API endpoint $api_url = 'https://maps.googleapis.com/maps/api/distancematrix/json'; // Build the request URL $params = [ 'origins' => $outlet_lat . ',' . $outlet_lng, 'destinations' => $latitude . ',' . $longitude, 'mode' => 'driving', 'units' => 'metric', 'key' => GOOGLE_DISTANCE_MATRIX_API_KEY ]; $url = $api_url . '?' . http_build_query($params); log_message('info', 'CalculateService: Requesting distance from Google API - Outlet: ' . $outlet_id . ', Customer: ' . $latitude . ',' . $longitude); // Make the API request using the helper function $response = send_api_request('GET', $url, [], null, true); if ($response === false) { log_message('error', 'CalculateService: Failed to get response from Google Distance Matrix API'); return 0; } $data = $response; // Check for API errors if ($data['status'] !== 'OK') { $error_message = $data['error_message'] ?? $data['status'] ?? 'Unknown error'; log_message('error', 'CalculateService: Google Distance Matrix API error: ' . $error_message); // Log additional error details if available if (isset($data['error_message'])) { log_message('error', 'CalculateService: Google API Error Message: ' . $data['error_message']); } if (isset($data['status'])) { log_message('error', 'CalculateService: Google API Status: ' . $data['status']); } return 0; } // Extract distance from response if (isset($data['rows'][0]['elements'][0]['distance']['value'])) { $distance_meters = $data['rows'][0]['elements'][0]['distance']['value']; $distance_km = $distance_meters / 1000; log_message('info', 'CalculateService: Distance calculated successfully - Outlet: ' . $outlet_id . ', Distance: ' . $distance_km . ' km'); return round($distance_km, 2); } else { // Check if the route is not found if (isset($data['rows'][0]['elements'][0]['status']) && $data['rows'][0]['elements'][0]['status'] === 'NOT_FOUND') { log_message('warning', 'CalculateService: Route not found between outlet and customer location'); } else { log_message('warning', 'CalculateService: No distance data in Google API response'); } return 0; } } catch (Exception $e) { log_message('error', 'CalculateService: Exception in calculateDistance: ' . $e->getMessage()); log_message('error', 'CalculateService: Stack trace: ' . $e->getTraceAsString()); return 0; } } /** * Calculate delivery fee based on distance * @param float $distance Distance in kilometers * @return float Delivery fee in RM */ public function calculateDeliveryFee($distance) { // Validate input if (!is_numeric($distance) || $distance < 0) { log_message('warning', 'CalculateService: Invalid distance provided for fee calculation: ' . $distance); return 0; } if ($distance == 0) { return 0; } // Delivery fee structure based on distance $fee = 0; //get setting delivery fee $setting_delivery_fee = $this->setting_delivery_fee->findAll(); foreach($setting_delivery_fee as $setting){ if($distance >= $setting['start_km'] && $distance <= $setting['end_km']){ $fee = $setting['price_per_km']; } } // Ensure fee is a valid number and round to 2 decimal places $fee = round($fee, 2); log_message('info', 'CalculateService: Delivery fee calculated - Distance: ' . $distance . ' km, Fee: RM ' . $fee); return $fee; } }