|
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 : /var/www/laciasmara.com/public_html/shop/application/controllers/ |
Upload File : |
<?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('stocks_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);
// echo "Cart items";
// echo "<pre>";
// print_r(json_encode($cart_items, JSON_PRETTY_PRINT));
// echo "</pre>";
// exit();
$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);
$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['browser_title'] .= ' - Checkout';
$this->data_header['meta_description'] = $meta_description;
$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['type'] === 'asmaradoor') {
$vouchers[] = [
'name' => 'Voucher: ' . $customer->refferal,
'type' => 'percentage',
'value' => 10,
'code' => $customer->refferal
];
} elseif ($affiliator['type'] === 'asmarasana') {
$vouchers[] = [];
}
}
}
// 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('type')
->from('affiliators')
->where('referral_code', $referral)
->where('status', 'active')
->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;
}
// $ineligible_product_ids = [64, 277, 386];
// foreach ($cart_items as $item) {
// if (in_array(intval($item['product_id']), $ineligible_product_ids)) {
// 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,tiki',
'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 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'];
$affiliate_data = get_affiliate_from_session();
$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'] . (!empty($input['address_note']) ? ' (' . $input['address_note'] . ')' : '')
: 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',
];
if ($affiliate_data) {
$orderData['tracking_visit_id'] = $affiliate_data['visit_id'];
$orderData['tracking_link_id'] = $affiliate_data['link_id'];
$orderData['tracking_affiliator_id'] = $affiliate_data['affiliator_id'];
// Keep referral field for backward compatibility
if (empty($orderData['referral'])) {
$orderData['referral'] = $affiliate_data['referral_code'];
}
log_message('info', 'Order has affiliate tracking data: Affiliator ' .
$affiliate_data['affiliator_id'] . ', Visit ' . $affiliate_data['visit_id']);
}
$grandTotal = (int) $input['grandTotalAmount'];
$customerId = $input['customerId'];
$minusReward = isset($input['minusPointReward']) ? (int)$input['minusPointReward'] : 0;
// Validasi input di awal
if ($minusReward < 0) {
echo json_encode(['error' => 'Invalid point reward value.']);
http_response_code(400);
return;
}
if (empty($customerId) || !is_numeric($customerId)) {
echo json_encode(['error' => 'Invalid customer ID.']);
http_response_code(400);
return;
}
// MULAI SATU TRANSACTION UNTUK SEMUA OPERASI
$this->db->trans_start();
try {
// 1. Insert order
$this->db->insert('orders', $orderData);
if ($this->db->affected_rows() === 0) {
throw new Exception('Failed to create order - no rows affected.');
}
$orderId = $this->db->insert_id();
// 2. Update customer points jika ada pengurangan
if ($minusReward > 0) {
// Cek customer dan current points
$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);
}
// Update points
$newPoints = $currentPoints - $minusReward;
$this->db->set('current_pointreward', $newPoints);
$this->db->where('id_customers', $customerId);
$this->db->update('customers');
if ($this->db->affected_rows() === 0) {
throw new Exception('Failed to update customer points - no rows affected.');
}
}
// 3. 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.');
}
// 4. Commit transaction SEBELUM membuat log
$this->db->trans_complete();
// Cek apakah transaction berhasil
if ($this->db->trans_status() === FALSE) {
throw new Exception('Transaction failed.');
}
// Response sukses
echo json_encode([
'message' => 'Order created successfully.',
'order_id' => $orderId,
'has_affiliate_tracking' => $affiliate_data !== null
]);
http_response_code(201);
} catch (Exception $e) {
// Rollback jika ada error
$this->db->trans_rollback();
echo json_encode(['error' => 'Database error occurred: ' . $e->getMessage()]);
http_response_code(500);
}
}
public function create_order_detail()
{
try {
// Validate and decode input data
$order_details = $this->validate_and_decode_input();
$order_id = $this->extract_order_id($order_details);
// Start database transaction
$this->db->trans_start();
// Check stock availability for all items
$stock_check_result = $this->check_stock_availability($order_details);
if (!$stock_check_result['success']) {
// Delete the main order since we can't fulfill it
$this->delete_order($order_id);
throw new Exception('Insufficient stock for some items', 422);
}
// Process each order detail
$process_result = $this->process_order_details($order_details, $stock_check_result['initial_stock_data']);
$this->db->trans_complete();
// Prepare response
$response_data = $this->prepare_response($process_result, count($order_details));
// Create log and send notification ONLY after everything is successful
if ($this->db->trans_status() !== FALSE && $process_result['success_count'] > 0) {
$this->create_order_log_and_notification($order_id);
}
return $response_data;
} catch (Exception $e) {
return $this->handle_exception($e);
}
}
/**
* Create order log and send notification after successful order completion
*/
private function create_order_log_and_notification($order_id)
{
try {
// Get order data for notification
$order_data = $this->get_order_data($order_id);
if (!$order_data) {
log_message('error', "Order data not found for ID: {$order_id}");
return;
}
$name = 'System';
$recipient = $order_data['recipient_name'];
$grand_total = $order_data['grand_total_amount'];
$description = "Pesanan Baru dari {$recipient} telah ditambahkan dengan Order ID {$order_id} senilai {$grand_total}. Pesanan ini dibuat oleh {$name}.";
$reference_url = base_url('admin/orders/manage-order?tab=pending');
// Create log and send notification
$log_id = $this->log_m->log_order_create($order_id, $description, $reference_url);
$this->log_m->send_order_notifications('CREATE_ORDER', $log_id, $description);
} catch (Exception $e) {
log_message('error', "Failed to create order log/notification for Order ID {$order_id}: " . $e->getMessage());
}
}
private function get_order_data($order_id)
{
$this->db->select('recipient_name, grand_total_amount');
$this->db->where('id_orders', $order_id);
$query = $this->db->get('orders');
return $query->num_rows() > 0 ? $query->row_array() : null;
}
/**
* Validate and decode JSON input data
*/
private function validate_and_decode_input()
{
if (!isset($_POST['orderDetailData'])) {
throw new Exception('Order detail data is missing');
}
$order_details = json_decode($_POST['orderDetailData'], true);
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');
}
return $order_details;
}
/**
* Extract order ID from order details
*/
private function extract_order_id($order_details)
{
$order_id = isset($order_details[0]['orderId']) ? $order_details[0]['orderId'] : null;
if (!$order_id) {
throw new Exception('Unable to determine order ID');
}
return $order_id;
}
/**
* Check stock availability for all items
*/
private function check_stock_availability($order_details)
{
$insufficient_stock_items = [];
$initial_stock_data = [];
foreach ($order_details as $detail) {
// Validate required fields
$this->validate_required_fields($detail);
// Get current stock
$stock_data = $this->get_current_stock_for_order($detail);
if (!$stock_data) {
$insufficient_stock_items[] = [
'product_name' => $detail['productName'],
'error' => 'Stock data not found'
];
continue;
}
// Store initial stock
$initial_stock_data[$detail['detailId']] = $stock_data['stock'];
// Check if stock is sufficient
if ($stock_data['stock'] < $detail['quantity']) {
$insufficient_stock_items[] = [
'product_name' => $detail['productName'],
'requested' => $detail['quantity'],
'available' => $stock_data['stock'],
'error' => 'Insufficient stock'
];
}
}
return [
'success' => empty($insufficient_stock_items),
'insufficient_items' => $insufficient_stock_items,
'initial_stock_data' => $initial_stock_data
];
}
/**
* Validate required fields for order detail
*/
private function validate_required_fields($detail)
{
$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}");
}
}
}
/**
* Get current stock data for order item
*/
private function get_current_stock_for_order($detail)
{
$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);
$stock_query = $this->db->get('stock');
return $stock_query->num_rows() > 0 ? $stock_query->row_array() : null;
}
/**
* Process all order details
*/
private function process_order_details($order_details, $initial_stock_data)
{
$success_count = 0;
$error_details = [];
foreach ($order_details as $detail) {
$result = $this->process_single_order_detail($detail, $initial_stock_data);
if ($result['success']) {
$success_count++;
} else {
$error_details[] = [
'product_name' => $detail['productName'],
'error' => $result['error']
];
}
}
return [
'success_count' => $success_count,
'error_details' => $error_details
];
}
/**
* Process single order detail item
*/
private function process_single_order_detail($detail, $initial_stock_data)
{
try {
// Insert order detail
$insert_data = $this->prepare_order_detail_data($detail);
if (!$this->db->insert('orders_detail', $insert_data)) {
return ['success' => false, 'error' => 'Failed to insert order detail'];
}
// Update stock and log movement
$stock_result = $this->update_stock_and_log_movement($detail, $initial_stock_data);
return $stock_result;
} catch (Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* Prepare order detail data for insertion
*/
private function prepare_order_detail_data($detail)
{
return [
'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']
];
}
/**
* Update stock and log movement using consistent approach
*/
private function update_stock_and_log_movement($detail, $initial_stock_data)
{
$warehouse_id = isset($detail['warehouseId']) ? $detail['warehouseId'] : 1;
$quantity_sold = (int)$detail['quantity'];
// Update stock
$this->db->set('stock', 'stock - ' . $quantity_sold, false);
$this->db->where('id_product', $detail['productId']);
$this->db->where('id_product_detail', $detail['detailId']);
$this->db->where('warehouse_id', $warehouse_id);
if (!$this->db->update('stock')) {
return ['success' => false, 'error' => 'Failed to update stock'];
}
// Get updated stock data
$updated_stock = $this->get_current_stock_for_order($detail);
if (!$updated_stock) {
return ['success' => false, 'error' => 'Stock ID not found after update'];
}
$stock_id = $updated_stock['id'];
$new_stock = $updated_stock['stock'];
$old_stock = $initial_stock_data[$detail['detailId']];
// Generate remark using stocks_m model
$remark = $this->stocks_m->generate_remark('SALES_ORDER', $detail['sku'], [
'qty' => $quantity_sold,
'order_no' => $detail['orderId'],
'customer' => $this->get_customer_name($detail['orderId']),
'remaining' => $new_stock
]);
// Log stock movement using stocks_m model
$movement_success = $this->stocks_m->log_stock_movement(
$stock_id,
'-',
$quantity_sold,
$new_stock,
$remark
);
if (!$movement_success) {
return ['success' => false, 'error' => 'Failed to log stock movement'];
}
// Log stock update (existing log system)
$this->log_m->log_stock_update(
$detail['orderId'],
$detail['detailId'],
$detail['productName'],
$old_stock,
$new_stock,
base_url('admin/products/stock-product?tab=all'),
null,
'stock',
'orders'
);
return ['success' => true];
}
/**
* Get customer name for the order (implement based on your order structure)
*/
private function get_customer_name($order_id)
{
// Implement this method to get customer name from orders table
$this->db->select('recipient_name'); // Adjust field name as needed
$this->db->where('id_orders', $order_id);
$query = $this->db->get('orders');
if ($query->num_rows() > 0) {
$row = $query->row();
return isset($row->recipient_name) ? $row->recipient_name : 'Unknown Customer';
}
return 'Unknown Customer';
}
/**
* Delete order when stock is insufficient
*/
private function delete_order($order_id)
{
$this->db->where('id_orders', $order_id);
$this->db->delete('orders');
}
/**
* Prepare response based on processing results
*/
private function prepare_response($process_result, $total_processed)
{
$response = $this->db->trans_status() === FALSE
? [
'status' => false,
'message' => 'Gagal menyimpan order detail',
'error_details' => $process_result['error_details']
]
: [
'status' => true,
'message' => "Berhasil menyimpan {$process_result['success_count']} order detail",
'total_processed' => $total_processed,
'success_count' => $process_result['success_count']
];
return $this->output
->set_content_type('application/json')
->set_output(json_encode($response));
}
/**
* Handle exceptions with proper error response
*/
private function handle_exception($e)
{
// Rollback transaction if it's active
if ($this->db->trans_status() !== FALSE) {
$this->db->trans_rollback();
}
$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();
$this->db->select('customer_id, minus_reward, minus_reward_amount');
$this->db->where('id_orders', $order_id);
$order_query = $this->db->get('orders');
if ($order_query->num_rows() === 0) {
throw new Exception('Order not found with ID: ' . $order_id);
}
$order_data = $order_query->row();
$customer_id = $order_data->customer_id;
$minus_reward = (int)$order_data->minus_reward;
if ($minus_reward > 0 && !empty($customer_id)) {
// Get current customer points
$this->db->select('current_pointreward');
$this->db->where('id_customers', $customer_id);
$customer_query = $this->db->get('customers');
if ($customer_query->num_rows() > 0) {
$customer = $customer_query->row();
$current_points = (int)$customer->current_pointreward;
// Restore the points that were deducted
$restored_points = $current_points + $minus_reward;
$this->db->set('current_pointreward', $restored_points);
$this->db->where('id_customers', $customer_id);
$this->db->update('customers');
if ($this->db->affected_rows() === 0) {
throw new Exception('Failed to restore customer points');
}
log_message('info', "Restored {$minus_reward} points to customer {$customer_id}. New total: {$restored_points}");
}
}
if (!empty($customer_id)) {
$this->db->set('is_first', 'GREATEST(is_first - 1, 0)', FALSE);
$this->db->where('id_customers', $customer_id);
$this->db->update('customers');
if ($this->db->affected_rows() === 0) {
// This might be OK if is_first was already 0, so just log it
log_message('info', "No changes to is_first for customer {$customer_id} (might already be 0)");
}
}
// 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 - transaction failed');
}
// Log the successful deletion
log_message('info', "Order {$order_id} successfully deleted with full rollback. Customer: {$customer_id}, Points restored: {$minus_reward}, Details deleted: {$affected_details}");
// Return success response
$response = [
'status' => true,
'message' => 'Pesanan berhasil dihapus dengan rollback lengkap',
'deleted_order_id' => $order_id,
'affected_details' => $affected_details,
'affected_orders' => $affected_orders,
'points_restored' => $minus_reward,
'customer_id' => $customer_id
];
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();
}
// Log the error
log_message('error', "Failed to delete order {$order_id}: " . $e->getMessage());
// Return error response
$response = [
'status' => false,
'message' => $e->getMessage(),
'order_id' => isset($order_id) ? $order_id : null
];
return $this->output
->set_content_type('application/json')
->set_status_header(500)
->set_output(json_encode($response));
}
}
// Validator JSON
public function test_etd()
{
return testCases();
}
}