function decreasec_stock($product, $quantity) {
$current_stock = get_field("stock_$product", 'option'); // Ambil stok dari Option Page
if ($current_stock === false || $current_stock === null) {
error_log("Stok untuk $product tidak ditemukan atau null.");
return;
}
// Pastikan stok adalah integer
$current_stock = (int) $current_stock;
// Log stok yang ditemukan
error_log("Current stock for $product: $current_stock");
if ($current_stock > 0 && $current_stock >= $quantity) {
$new_stock = $current_stock - $quantity; // Kurangi stok
update_field("stock_$product", $new_stock, 'option'); // Perbarui stok di Option Page
error_log("Stok untuk $product dikurangi menjadi $new_stock");
} else {
error_log("Tidak cukup stok untuk $product. Stok saat ini: $current_stock");
}
}
function get_monthly_sales_report($month, $year) {
// Query untuk mengambil data dari CPT rental-order (penjualan)
$sales_args = array(
'post_type' => 'rental-order',
'post_status' => 'publish',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'payment_status',
'value' => 'Sudah Bayar',
'compare' => '='
),
),
'date_query' => array(
array(
'year' => $year,
'month' => $month,
),
),
);
$sales_query = new WP_Query($sales_args);
$total_sales = 0;
$transaction_count = 0;
$total_shipping_cost = 0;
if ($sales_query->have_posts()) {
while ($sales_query->have_posts()) {
$sales_query->the_post();
$total_cost = get_post_meta(get_the_ID(), 'total_cost', true);
$shipping_cost = get_post_meta(get_the_ID(), 'shipping_cost', true);
$total_sales += (float) $total_cost;
$total_shipping_cost += (float) $shipping_cost;
$transaction_count++;
}
}
wp_reset_postdata();
// Query untuk mengambil data dari CPT rental-expense (pengeluaran)
$expense_args = array(
'post_type' => 'rental-expense',
'post_status' => 'publish',
'posts_per_page' => -1,
'date_query' => array(
array(
'year' => $year,
'month' => $month,
),
),
);
$expense_query = new WP_Query($expense_args);
$total_expense = 0;
if ($expense_query->have_posts()) {
while ($expense_query->have_posts()) {
$expense_query->the_post();
$cost_expense = get_post_meta(get_the_ID(), 'cost-expense', true);
$total_expense += (float) $cost_expense;
}
}
wp_reset_postdata();
// Menghitung net profit
$net_profit = $total_sales - $total_expense - $total_shipping_cost;
return array(
'total_sales' => $total_sales,
'transaction_count' => $transaction_count,
'total_shipping_cost' => $total_shipping_cost,
'total_expense' => $total_expense,
'net_profit' => $net_profit
);
}
// Shortcode untuk menampilkan total sales, transaction count, shipping cost, expenses, dan net profit
add_shortcode('monthly_sales_report', function($atts) {
// Atribut default (default "total_sales")
$atts = shortcode_atts(
array(
'type' => 'total_sales', // Menentukan apa yang ditampilkan: "total_sales", "transaction_count", "shipping_cost", "total_expense", atau "net_profit"
),
$atts,
'monthly_sales_report'
);
$current_month = date('m'); // Bulan saat ini
$current_year = date('Y'); // Tahun saat ini
$report = get_monthly_sales_report($current_month, $current_year);
// Memilih data sesuai dengan atribut "type"
if ($atts['type'] == 'total_sales') {
return 'Rp' . number_format($report['total_sales'], 0, ',', '.');
} elseif ($atts['type'] == 'transaction_count') {
return $report['transaction_count'];
} elseif ($atts['type'] == 'shipping_cost') {
return 'Rp' . number_format($report['total_shipping_cost'], 0, ',', '.');
} elseif ($atts['type'] == 'total_expense') {
return 'Rp' . number_format($report['total_expense'], 0, ',', '.');
} elseif ($atts['type'] == 'net_profit') {
// Jika net profit negatif, tampilkan - Rp100.000 (tanpa spasi, tanpa tanda minus ganda)
if ($report['net_profit'] < 0) {
return '- Rp' . number_format(abs($report['net_profit']), 0, ',', '.');
}
return 'Rp' . number_format($report['net_profit'], 0, ',', '.');
}
return ''; // Mengembalikan kosong jika tidak ada tipe yang valid
});
add_filter( 'bricks/dynamic_tags_list', 'add_bulan_indo_tag_to_builder' );
function add_bulan_indo_tag_to_builder( $tags ) {
// Menambahkan tag dinamis baru untuk bulan dalam bahasa Indonesia
$tags[] = [
'name' => '{bulan_indo}',
'label' => 'Bulan Indonesia',
'group' => 'Custom Dynamic Data',
];
return $tags;
}
add_filter( 'bricks/dynamic_data/render_tag', 'get_bulan_indo_value', 20, 3 );
function get_bulan_indo_value( $tag, $post, $context = 'text' ) {
// Memeriksa apakah tag adalah {bulan_indo}
if ( $tag !== '{bulan_indo}' ) {
return $tag;
}
// Mendapatkan bulan dalam format Indonesia
$value = run_bulan_indo_logic();
return $value;
}
function run_bulan_indo_logic() {
// Array bulan dalam bahasa Indonesia
$bulan_indo = [
1 => 'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
];
// Mendapatkan bulan saat ini
$bulan = date('n'); // Mengambil bulan dalam angka (1-12)
// Mengembalikan nama bulan dalam bahasa Indonesia
return $bulan_indo[$bulan];
}
add_filter( 'bricks/dynamic_data/render_content', 'render_bulan_indo_tag', 20, 3 );
add_filter( 'bricks/frontend/render_data', 'render_bulan_indo_tag', 20, 2 );
function render_bulan_indo_tag( $content, $post, $context = 'text' ) {
// Memeriksa apakah tag {bulan_indo} ada dalam konten
if ( strpos( $content, '{bulan_indo}' ) === false ) {
return $content;
}
// Menjalankan logika untuk mendapatkan bulan dalam bahasa Indonesia
$bulan_indo = run_bulan_indo_logic();
// Mengganti tag {bulan_indo} dengan bulan dalam bahasa Indonesia
$content = str_replace( '{bulan_indo}', $bulan_indo, $content );
return $content;
}
/**
* Rental System API for WordPress
* Handles stock updates, order management, and WhatsApp notifications.
*
* @package RentalSystem
* @version 1.0.11
*/
defined('ABSPATH') || exit;
/**
* Rental System class encapsulating all functionality
*/
class RentalSystem {
private const UNIT_MAPPING = [
'PS3TV' => ['ps3' => 1, 'tv' => 1],
'PS4TV' => ['ps4' => 1, 'tv' => 1],
'PS3' => ['ps3' => 1],
'PS4' => ['ps4' => 1],
'TV' => ['tv' => 1],
];
private const DURATION_MAPPING = [
'6H' => 6, '6 JAM' => 6,
'12H' => 12, '12 JAM' => 12,
'24H' => 24, '1 HARI' => 24,
'48H' => 48, '2 HARI' => 48,
'72H' => 72, '3 HARI' => 72,
'96H' => 96, '4 HARI' => 96,
'120H' => 120, '5 HARI' => 120,
];
/**
* Initialize the plugin
*/
public static function init() {
add_action('rest_api_init', [__CLASS__, 'register_endpoints']);
add_action('decrease_stock_scheduled', [__CLASS__, 'execute_stock_decrease'], 10, 2);
add_action('set_status_pending_scheduled', [__CLASS__, 'execute_set_status_pending'], 10, 1);
add_action('send_whatsapp_message_template_2', [__CLASS__, 'send_whatsapp_template_2'], 10, 3);
add_action('send_whatsapp_message_template_3', [__CLASS__, 'send_whatsapp_template_3'], 10, 3);
add_action('wp_cron', [__CLASS__, 'log_cron_execution']);
}
/**
* Register REST API endpoints
*/
public static function register_endpoints() {
register_rest_route('websee/v1', '/sync-stock', [
'methods' => WP_REST_Server::CREATABLE,
'callback' => [__CLASS__, 'handle_sync_stock_request'],
'permission_callback' => '__return_true',
]);
}
/**
* Handle webhook request for stock sync
*
* @param WP_REST_Request $request
* @return WP_REST_Response
*/
public static function handle_sync_stock_request(WP_REST_Request $request) {
$data = $request->get_json_params();
self::log_message('INFO', 'Webhook received', ['data' => $data]);
// Validate required fields
$validation = self::validate_request_data($data);
if (is_array($validation)) {
self::log_message('ERROR', 'Missing required fields', ['fields' => $validation]);
return new WP_REST_Response(['message' => 'Missing fields: ' . implode(', ', $validation)], 400);
}
$post_id = intval($data['post-id']);
$order_status = sanitize_text_field($data['order-status-api']);
$payment_status = sanitize_text_field($data['payment-status-api']);
$unit_name = trim(sanitize_text_field($data['unit-name-api']));
$pickup_date = isset($data['pickup-date']) && is_string($data['pickup-date']) ? sanitize_text_field(trim(str_replace(' ,', ',', $data['pickup-date']))) : '';
$return_date = isset($data['return-date']) && is_string($data['return-date']) ? sanitize_text_field(trim(str_replace(' ,', ',', $data['return-date']))) : '';
$phone_number = self::normalize_phone_number(
isset($data['phone_number-api']) && !empty(trim($data['phone_number-api']))
? (string) $data['phone_number-api']
: get_field('phone_number', $post_id)
);
self::log_message('INFO', 'Processing order', [
'post_id' => $post_id,
'unit' => $unit_name,
'order_status' => $order_status,
'payment_status' => $payment_status,
'pickup_date' => $pickup_date,
'return_date' => $return_date,
'phone_number' => $phone_number,
]);
// Validate post and unit
if (!get_post($post_id)) {
self::log_message('ERROR', 'Invalid post ID', ['post_id' => $post_id]);
return new WP_REST_Response(['message' => 'Invalid post ID'], 404);
}
if (!isset(self::UNIT_MAPPING[$unit_name])) {
self::log_message('ERROR', 'Invalid unit name', ['unit_name' => $unit_name]);
return new WP_REST_Response(['message' => 'Invalid unit name'], 400);
}
// Update basic fields
$fields = [
'status-order' => $order_status,
'payment_status' => $payment_status,
'unit' => $unit_name,
'phone_number' => $phone_number,
];
foreach ($fields as $key => $value) {
update_field($key, $value, $post_id);
}
// Check if payment status is valid for scheduling
//$is_paid = in_array($payment_status, ['Sudah_DP', 'Sudah Bayar', ]);
$is_paid = in_array($payment_status, ['Sudah_DP', 'Sudah Bayar', 'Belum Bayar']);
// Handle status-specific logic
switch ($order_status) {
case 'Running':
self::handle_running_status($post_id, $unit_name, $phone_number, $payment_status, $is_paid);
break;
case 'Selesai':
self::handle_completed_status($post_id, $unit_name, $phone_number, $is_paid);
break;
case 'Dibatalkan':
self::handle_cancelled_status($post_id, $unit_name);
break;
case 'Dipesan':
case 'Pending':
self::handle_pending_status($post_id, $unit_name, $phone_number, $payment_status, $pickup_date, $return_date, $is_paid, $order_status);
break;
default:
self::log_message('ERROR', 'Unknown order status', ['status' => $order_status]);
return new WP_REST_Response(['message' => 'Unknown order status'], 400);
}
self::log_message('INFO', 'Stock scheduling completed', ['post_id' => $post_id]);
return new WP_REST_Response(['message' => 'Stock update scheduled successfully'], 200);
}
/**
* Validate request data
*
* @param array $data
* @return array|true
*/
private static function validate_request_data($data) {
$required = ['post-id', 'order-status-api', 'payment-status-api', 'unit-name-api'];
$errors = [];
foreach ($required as $field) {
if (empty(trim($data[$field] ?? ''))) {
$errors[] = $field;
}
}
return $errors ?: true;
}
/**
* Normalize phone number to E.164 format
*
* @param string $phone
* @return string
*/
private static function normalize_phone_number($phone) {
$phone = trim(sanitize_text_field($phone));
if (empty($phone)) {
return '';
}
if (substr($phone, 0, 2) === '08') {
$phone = '628' . substr($phone, 2);
} elseif (substr($phone, 0, 1) === '8') {
$phone = '628' . substr($phone, 1);
} elseif (substr($phone, 0, 3) !== '628') {
$phone = '628' . $phone;
}
if (!preg_match('/^628[0-9]{8,12}$/', $phone)) {
self::log_message('WARNING', 'Invalid phone number format', ['phone' => $phone]);
return '';
}
return $phone;
}
/**
* Parse date in WIB timezone
*
* @param string $date_str
* @return DateTime|false
*/
private static function parse_wib_date($date_str) {
if (empty($date_str) || !is_string($date_str)) {
self::log_message('ERROR', 'Invalid date string', ['date' => $date_str]);
return false;
}
$clean_date = trim(str_replace(' ,', ',', $date_str));
$date = DateTime::createFromFormat('d-m-Y, H:i', $clean_date, new DateTimeZone('Asia/Jakarta'));
if (!$date) {
self::log_message('ERROR', 'Failed to parse date', ['date' => $clean_date, 'format' => 'd-m-Y, H:i']);
return false;
}
return $date;
}
/**
* Handle Running status
*
* @param int $post_id
* @param string $unit_name
* @param string $phone_number
* @param string $payment_status
* @param bool $is_paid
*/
private static function handle_running_status($post_id, $unit_name, $phone_number, $payment_status, $is_paid) {
// Set pickup_date to now in WIB
$wib_timezone = new DateTimeZone('Asia/Jakarta');
$current_datetime = new DateTime('now', $wib_timezone);
$pickup_date = $current_datetime->format('d-m-Y, H:i');
// Calculate return_date based on duration
$duration_value = get_field('duration', $post_id);
if (is_array($duration_value)) {
$duration_value = reset($duration_value);
}
$duration_value = trim(sanitize_text_field($duration_value));
if (empty($duration_value)) {
self::log_message('WARNING', 'No duration found, defaulting to 6 hours', ['post_id' => $post_id]);
}
$hours_to_add = self::DURATION_MAPPING[strtoupper($duration_value)] ?? 6;
$return_datetime = clone $current_datetime;
$return_datetime->modify("+{$hours_to_add} hours");
$return_date = $return_datetime->format('d-m-Y, H:i');
self::log_message('INFO', 'Running status dates updated', [
'post_id' => $post_id,
'pickup_date' => $pickup_date,
'return_date' => $return_date,
]);
// Update dates
update_post_meta($post_id, 'pickup_date', $pickup_date);
update_post_meta($post_id, 'return_date', $return_date);
// Cancel all existing schedulers
self::cancel_all_order_schedulers($post_id);
// Proceed only if payment is valid
if ($is_paid) {
// Schedule stock decrease
if (!get_post_meta($post_id, 'has-stock-decreased', true)) {
self::schedule_stock_decrease($post_id, $unit_name, $pickup_date);
}
// Send template_1 and schedule WhatsApp messages
if ($phone_number) {
self::send_whatsapp_message('template_1', $post_id, 'Running', $payment_status, $phone_number);
self::schedule_whatsapp_message($post_id, $unit_name, $phone_number, $return_date, $payment_status);
} else {
self::log_message('WARNING', 'No phone number for WhatsApp', ['post_id' => $post_id]);
}
// Schedule Pending status
self::schedule_pending_status($post_id, $return_date);
} else {
self::log_message('INFO', 'Skipped scheduling due to unpaid status', [
'post_id' => $post_id,
'payment_status' => $payment_status,
]);
}
}
/**
* Handle Completed status
*
* @param int $post_id
* @param string $unit_name
* @param string $phone_number
* @param bool $is_paid
*/
private static function handle_completed_status($post_id, $unit_name, $phone_number, $is_paid) {
self::cancel_all_order_schedulers($post_id);
if ($is_paid && get_post_meta($post_id, 'has-stock-decreased', true)) {
self::restore_stock($unit_name);
delete_post_meta($post_id, 'has-stock-decreased');
self::log_message('INFO', 'Stock restored', ['post_id' => $post_id, 'unit' => $unit_name]);
}
if ($is_paid && $phone_number) {
self::send_whatsapp_message('template_4', $post_id, 'Selesai', 'Sudah Bayar', $phone_number);
}
self::log_message('INFO', 'Processed Selesai status', ['post_id' => $post_id]);
}
/**
* Handle Cancelled status
*
* @param int $post_id
* @param string $unit_name
*/
private static function handle_cancelled_status($post_id, $unit_name) {
self::cancel_all_order_schedulers($post_id);
if (get_post_meta($post_id, 'has-stock-decreased', true)) {
self::restore_stock($unit_name);
delete_post_meta($post_id, 'has-stock-decreased');
self::log_message('INFO', 'Stock restored', ['post_id' => $post_id, 'unit' => $unit_name]);
}
$updated_post = wp_update_post([
'ID' => $post_id,
'post_status' => 'trash',
]);
if (!is_wp_error($updated_post)) {
clean_post_cache($post_id);
wp_cache_delete($post_id, 'posts');
self::log_message('INFO', 'Post moved to trash', ['post_id' => $post_id]);
} else {
self::log_message('ERROR', 'Failed to trash post', ['post_id' => $post_id]);
}
}
/**
* Handle Pending/Dipesan status
*
* @param int $post_id
* @param string $unit_name
* @param string $phone_number
* @param string $payment_status
* @param string $pickup_date
* @param string $return_date
* @param bool $is_paid
* @param string $order_status
*/
private static function handle_pending_status($post_id, $unit_name, $phone_number, $payment_status, $pickup_date, $return_date, $is_paid, $order_status) {
// Use existing pickup_date and return_date from post meta if input is empty
$existing_pickup_date = $pickup_date ?: get_post_meta($post_id, 'pickup_date', true) ?: '';
$existing_return_date = $return_date ?: get_post_meta($post_id, 'return_date', true) ?: '';
// Update only if new values are provided
if ($pickup_date) {
update_post_meta($post_id, 'pickup_date', $pickup_date);
$existing_pickup_date = $pickup_date;
}
if ($return_date) {
update_post_meta($post_id, 'return_date', $return_date);
$existing_return_date = $return_date;
}
// Cancel existing schedulers to ensure clean state
self::cancel_all_order_schedulers($post_id);
if ($is_paid) {
// If no pickup_date exists, set to current time
if (empty($existing_pickup_date)) {
$wib_timezone = new DateTimeZone('Asia/Jakarta');
$current_datetime = new DateTime('now', $wib_timezone);
$existing_pickup_date = $current_datetime->format('d-m-Y, H:i');
update_post_meta($post_id, 'pickup_date', $existing_pickup_date);
self::log_message('INFO', 'Set default pickup_date for Dipesan', [
'post_id' => $post_id,
'pickup_date' => $existing_pickup_date,
]);
}
// If no return_date exists, calculate based on duration
if (empty($existing_return_date)) {
$duration_value = get_field('duration', $post_id);
if (is_array($duration_value)) {
$duration_value = reset($duration_value);
}
$duration_value = trim(sanitize_text_field($duration_value));
$hours_to_add = self::DURATION_MAPPING[strtoupper($duration_value)] ?? 6;
$pickup_date_obj = self::parse_wib_date($existing_pickup_date);
if ($pickup_date_obj) {
$return_datetime = clone $pickup_date_obj;
$return_datetime->modify("+{$hours_to_add} hours");
$existing_return_date = $return_datetime->format('d-m-Y, H:i');
update_post_meta($post_id, 'return_date', $existing_return_date);
self::log_message('INFO', 'Set default return_date for Dipesan', [
'post_id' => $post_id,
'return_date' => $existing_return_date,
]);
} else {
self::log_message('WARNING', 'Failed to parse pickup_date for return_date calculation', [
'post_id' => $post_id,
'pickup_date' => $existing_pickup_date,
]);
return; // Skip scheduling if dates are invalid
}
}
// Schedule stock decrease using existing pickup_date
if (!get_post_meta($post_id, 'has-stock-decreased', true) && $existing_pickup_date) {
self::schedule_stock_decrease($post_id, $unit_name, $existing_pickup_date);
}
// Send WhatsApp and schedule messages for Dipesan/Pending
if ($phone_number && $existing_return_date) {
if ($order_status === 'Pending') {
self::send_whatsapp_message('template_2', $post_id, 'Pending', $payment_status, $phone_number);
}
self::schedule_whatsapp_message($post_id, $unit_name, $phone_number, $existing_return_date, $payment_status);
} else {
self::log_message('WARNING', 'No phone number or return_date for WhatsApp scheduling', [
'post_id' => $post_id,
'phone_number' => $phone_number,
'return_date' => $existing_return_date,
]);
}
// Schedule Pending status
if ($existing_return_date) {
self::schedule_pending_status($post_id, $existing_return_date);
}
} else {
self::log_message('INFO', 'Skipped scheduling due to unpaid status', [
'post_id' => $post_id,
'payment_status' => $payment_status,
]);
}
}
/**
* Schedule stock decrease
*
* @param int $post_id
* @param string $unit_name
* @param string $pickup_date
*/
public static function schedule_stock_decrease($post_id, $unit_name, $pickup_date) {
self::log_message('INFO', 'Scheduling stock decrease', [
'post_id' => $post_id,
'unit' => $unit_name,
'pickup_date' => $pickup_date,
]);
$date_obj = self::parse_wib_date($pickup_date);
if (!$date_obj) {
return;
}
$date_obj->setTimezone(new DateTimeZone('UTC'));
$timestamp = $date_obj->getTimestamp();
$current_time = time();
$tolerance = 120; // 2 minutes
if ($timestamp < $current_time - $tolerance) {
self::log_message('ERROR', 'Pickup date too far in the past', ['post_id' => $post_id, 'timestamp' => $timestamp]);
return;
} elseif ($timestamp <= $current_time) {
self::log_message('INFO', 'Pickup date is now, executing stock decrease immediately', ['post_id' => $post_id]);
self::execute_stock_decrease($post_id, $unit_name);
return;
}
as_unschedule_action('decrease_stock_scheduled', [$post_id, $unit_name], "stock_decrease_{$post_id}");
as_schedule_single_action($timestamp, 'decrease_stock_scheduled', [$post_id, $unit_name], "stock_decrease_{$post_id}");
self::log_message('INFO', 'Scheduled stock decrease', [
'post_id' => $post_id,
'timestamp' => gmdate('Y-m-d H:i:s', $timestamp),
]);
}
/**
* Execute stock decrease
*
* @param int $post_id
* @param string $unit_name
*/
public static function execute_stock_decrease($post_id, $unit_name) {
$lock_key = "stock_decrease_{$post_id}";
if (get_transient($lock_key)) {
self::log_message('WARNING', 'Stock decrease locked', ['post_id' => $post_id]);
return;
}
set_transient($lock_key, true, 10);
if (empty($post_id) || empty($unit_name)) {
self::log_message('ERROR', 'Invalid parameters for stock decrease', ['post_id' => $post_id, 'unit' => $unit_name]);
delete_transient($lock_key);
return;
}
if (get_post_meta($post_id, 'has-stock-decreased', true)) {
self::log_message('WARNING', 'Stock already decreased, skipping', ['post_id' => $post_id]);
delete_transient($lock_key);
return;
}
self::handle_unit_stock_decrease($unit_name);
update_post_meta($post_id, 'has-stock-decreased', true);
delete_transient($lock_key);
self::log_message('INFO', 'Stock decreased', ['post_id' => $post_id, 'unit' => $unit_name]);
}
/**
* Decrease stock for unit
*
* @param string $unit_name
*/
private static function handle_unit_stock_decrease($unit_name) {
if (!isset(self::UNIT_MAPPING[$unit_name])) {
self::log_message('ERROR', 'Unknown unit', ['unit' => $unit_name]);
return;
}
foreach (self::UNIT_MAPPING[$unit_name] as $product => $qty) {
$field = "stock_{$product}";
$current_stock = (int) get_field($field, 'option');
$new_stock = $current_stock - $qty;
update_field($field, $new_stock, 'option');
self::log_message('INFO', 'Stock updated', [
'field' => $field,
'old' => $current_stock,
'new' => $new_stock,
]);
if ($new_stock < 0) {
self::log_message('WARNING', 'Negative stock', ['field' => $field, 'stock' => $new_stock]);
}
}
}
/**
* Restore stock for unit
*
* @param string $unit_name
*/
private static function restore_stock($unit_name) {
if (!isset(self::UNIT_MAPPING[$unit_name])) {
self::log_message('ERROR', 'Unknown unit', ['unit' => $unit_name]);
return;
}
foreach (self::UNIT_MAPPING[$unit_name] as $product => $qty) {
$field = "stock_{$product}";
$current_stock = (int) get_field($field, 'option');
$new_stock = $current_stock + $qty;
update_field($field, $new_stock, 'option');
self::log_message('INFO', 'Stock restored', [
'field' => $field,
'old' => $current_stock,
'new' => $new_stock,
]);
}
}
/**
* Schedule WhatsApp messages
*
* @param int $post_id
* @param string $unit_name
* @param string $phone_number
* @param string $return_date
* @param string $payment_status
*/
public static function schedule_whatsapp_message($post_id, $unit_name, $phone_number, $return_date, $payment_status) {
if (empty($phone_number)) {
self::log_message('WARNING', 'Cannot schedule WhatsApp: empty phone number', ['post_id' => $post_id]);
return;
}
//if (!in_array($payment_status, ['Sudah_DP', 'Sudah Bayar'])) {
if (!in_array($payment_status, ['Sudah_DP', 'Sudah Bayar', 'Belum Bayar'])) {
self::log_message('INFO', 'Skipped WhatsApp scheduling due to unpaid status', [
'post_id' => $post_id,
'payment_status' => $payment_status,
]);
return;
}
$order_status = get_field('status-order', $post_id);
if ($order_status === 'Dibatalkan') {
self::log_message('INFO', 'Skipped WhatsApp scheduling due to cancelled order', [
'post_id' => $post_id,
'order_status' => $order_status,
]);
return;
}
$return_date_obj = self::parse_wib_date($return_date);
if (!$return_date_obj) {
return;
}
$current_time = time();
$return_timestamp = $return_date_obj->setTimezone(new DateTimeZone('UTC'))->getTimestamp();
if ($return_timestamp < $current_time - 300) { // 5-minute tolerance
self::log_message('WARNING', 'Return date too far in the past', ['post_id' => $post_id, 'date' => $return_date]);
return;
}
$message_2_time = (clone $return_date_obj)->modify('-1 hour');
$timestamps = [
'template_2' => $message_2_time->setTimezone(new DateTimeZone('UTC'))->getTimestamp(),
'template_3' => $return_timestamp,
];
foreach ($timestamps as $template => $timestamp) {
if ($timestamp < $current_time) {
self::log_message('WARNING', "Skipping $template scheduling: timestamp in past", [
'post_id' => $post_id,
'timestamp' => gmdate('Y-m-d H:i:s', $timestamp),
]);
continue;
}
as_unschedule_action("send_whatsapp_message_{$template}", [$post_id, $unit_name, $phone_number], "whatsapp_{$post_id}_{$template}");
as_schedule_single_action($timestamp, "send_whatsapp_message_{$template}", [$post_id, $unit_name, $phone_number], "whatsapp_{$post_id}_{$template}");
self::log_message('INFO', "Scheduled $template", [
'post_id' => $post_id,
'timestamp' => gmdate('Y-m-d H:i:s', $timestamp),
]);
}
}
/**
* Send WhatsApp message with template
*
* @param string $template
* @param int $post_id
* @param string $order_status
* @param string $payment_status
* @param string $phone_number
*/
private static function send_whatsapp_message($template, $post_id, $order_status, $payment_status, $phone_number) {
if (empty($phone_number)) {
self::log_message('WARNING', 'Cannot send WhatsApp: empty phone number', ['post_id' => $post_id]);
return;
}
//if (!in_array($payment_status, ['Sudah_DP', 'Sudah Bayar'])) {
if (!in_array($payment_status, ['Sudah_DP', 'Sudah Bayar', 'Belum Bayar'])) {
self::log_message('INFO', 'Skipped WhatsApp sending due to unpaid status', [
'post_id' => $post_id,
'payment_status' => $payment_status,
]);
return;
}
// Get ACF fields, default to '-' if empty
$acf_fields = [
'customer_name' => get_field('customer_name', $post_id) ?: '-',
'locations' => get_field('locations', $post_id) ?: '-',
'unit' => get_field('unit', $post_id) ?: '-',
'duration' => get_field('duration', $post_id) ?: '-',
'pickup_date' => get_field('pickup_date', $post_id) ?: '-',
'return_date' => get_field('return_date', $post_id) ?: '-',
];
// Log if any fields are defaulted to '-'
$defaulted_fields = array_keys(array_filter($acf_fields, fn($value) => $value === '-'));
if ($defaulted_fields) {
self::log_message('INFO', 'Using default value for ACF fields', [
'post_id' => $post_id,
'fields' => $defaulted_fields,
]);
}
$max_retries = 3;
$attempt = 1;
while ($attempt <= $max_retries) {
try {
$response = send_message_with_template_zapin($template, $post_id, $order_status, $payment_status, $phone_number);
self::log_message('INFO', "WhatsApp $template sent", [
'post_id' => $post_id,
'attempt' => $attempt,
'response' => $response,
]);
return;
} catch (Exception $e) {
self::log_message('ERROR', "WhatsApp $template failed", [
'post_id' => $post_id,
'attempt' => $attempt,
'error' => $e->getMessage(),
]);
}
$attempt++;
sleep(1);
}
self::log_message('ERROR', "WhatsApp $template failed after $max_retries attempts", ['post_id' => $post_id]);
}
/**
* Send WhatsApp template 2
*
* @param int $post_id
* @param string $unit_name
* @param string $phone_number
*/
public static function send_whatsapp_template_2($post_id, $unit_name, $phone_number) {
if (empty($post_id) || empty($phone_number)) {
self::log_message('ERROR', 'Invalid parameters for template_2', [
'post_id' => $post_id,
'phone_number' => $phone_number,
]);
return;
}
$order_status = get_field('status-order', $post_id) ?: 'Pending';
$payment_status = get_field('payment_status', $post_id) ?: '';
self::send_whatsapp_message('template_2', $post_id, $order_status, $payment_status, $phone_number);
self::log_message('INFO', 'Executed template_2', ['post_id' => $post_id]);
}
/**
* Send WhatsApp template 3
*
* @param int $post_id
* @param string $unit_name
* @param string $phone_number
*/
public static function send_whatsapp_template_3($post_id, $unit_name, $phone_number) {
if (empty($post_id) || empty($phone_number)) {
self::log_message('ERROR', 'Invalid parameters for template_3', [
'post_id' => $post_id,
'phone_number' => $phone_number,
]);
return;
}
$order_status = get_field('status-order', $post_id) ?: 'Pending';
$payment_status = get_field('payment_status', $post_id) ?: '';
self::send_whatsapp_message('template_3', $post_id, $order_status, $payment_status, $phone_number);
self::log_message('INFO', 'Executed template_3', ['post_id' => $post_id]);
}
/**
* Schedule Pending status
*
* @param int $post_id
* @param string $return_date
*/
private static function schedule_pending_status($post_id, $return_date) {
$date_obj = self::parse_wib_date($return_date);
if (!$date_obj) {
return;
}
$timestamp = $date_obj->setTimezone(new DateTimeZone('UTC'))->getTimestamp();
as_unschedule_action('set_status_pending_scheduled', [$post_id], "pending_{$post_id}");
as_schedule_single_action($timestamp, 'set_status_pending_scheduled', [$post_id], "pending_{$post_id}");
self::log_message('INFO', 'Scheduled Pending status', [
'post_id' => $post_id,
'timestamp' => gmdate('Y-m-d H:i:s', $timestamp),
]);
}
/**
* Execute set status to Pending
*
* @param int $post_id
*/
public static function execute_set_status_pending($post_id) {
if (empty($post_id)) {
self::log_message('ERROR', 'Invalid post ID for Pending status');
return;
}
$payment_status = get_field('payment_status', $post_id);
//if (!in_array($payment_status, ['Sudah_DP', 'Sudah Bayar'])) {
if (!in_array($payment_status, ['Sudah_DP', 'Sudah Bayar', 'Belum Bayar'])) {
self::log_message('INFO', 'Skipped setting Pending status due to unpaid status', [
'post_id' => $post_id,
'payment_status' => $payment_status,
]);
return;
}
update_field('status-order', 'Pending', $post_id);
$phone_number = get_field('phone_number', $post_id);
if ($phone_number) {
self::send_whatsapp_message('template_2', $post_id, 'Pending', $payment_status, $phone_number);
}
self::log_message('INFO', 'Set status to Pending', ['post_id' => $post_id]);
}
/**
* Cancel all schedulers for an order
*
* @param int $post_id
* @return bool
*/
private static function cancel_all_order_schedulers($post_id) {
$unit_name = get_field('unit', $post_id) ?: '';
$phone_number = get_field('phone_number', $post_id) ?: '';
$total_cancelled = 0;
$actions = [
['hook' => 'decrease_stock_scheduled', 'args' => [$post_id, $unit_name], 'group' => "stock_decrease_{$post_id}"],
['hook' => 'set_status_pending_scheduled', 'args' => [$post_id], 'group' => "pending_{$post_id}"],
['hook' => 'send_whatsapp_message_template_2', 'args' => [$post_id, $unit_name, $phone_number], 'group' => "whatsapp_{$post_id}_template_2"],
['hook' => 'send_whatsapp_message_template_3', 'args' => [$post_id, $unit_name, $phone_number], 'group' => "whatsapp_{$post_id}_template_3"],
];
foreach ($actions as $action) {
$cancelled = as_unschedule_action($action['hook'], $action['args'], $action['group']);
$total_cancelled += $cancelled ? 1 : 0;
self::log_message('INFO', $cancelled ? "Cancelled {$action['hook']}" : "No task found for {$action['hook']}", [
'post_id' => $post_id,
'args' => $action['args'],
]);
}
self::log_message('INFO', 'Total schedulers cancelled', ['post_id' => $post_id, 'count' => $total_cancelled]);
return $total_cancelled > 0;
}
/**
* Log cron execution
*/
public static function log_cron_execution() {
self::log_message('INFO', 'WP-Cron executed', ['time' => gmdate('Y-m-d H:i:s')]);
}
/**
* Log message with structured format
*
* @param string $level
* @param string $message
* @param array $context
*/
private static function log_message($level, $message, $context = []) {
error_log(sprintf(
'[RentalSystem][%s] %s %s',
strtoupper($level),
$message,
!empty($context) ? json_encode($context) : ''
));
}
}
// Initialize the plugin
RentalSystem::init();function send_whatsapp_message_zapin($phone_number, $message) {
$api_key = 'wyj9HRX8kKqTE0hQKAI1WGAbGfJVNV'; // API key Anda
$sender = '6287856161934'; // Nomor pengirim WhatsApp
// Data yang akan dikirim ke API
$data = array(
'api_key' => $api_key,
'sender' => $sender,
'number' => $phone_number,
'message' => $message,
);
// Endpoint API
$url = 'https://zapin.my.id/send-message';
// Inisialisasi cURL
$ch = curl_init();
// Set opsi untuk permintaan cURL
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); // Mengirim data dalam format JSON
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); // Set header Content-Type ke application/json
// Eksekusi cURL dan ambil respons
$result = curl_exec($ch);
// Cek apakah ada kesalahan saat eksekusi cURL
// Cek apakah ada kesalahan saat eksekusi cURL
if(curl_errno($ch)) {
error_log('❌ Gagal mengirim pesan WhatsApp ke nomor ' . $phone_number . ': ' . curl_error($ch)); // Log error jika terjadi masalah
} else {
error_log('✅ Pesan WhatsApp berhasil dikirim ke nomor ' . $phone_number . '.');
}
// Tutup cURL
curl_close($ch);
}
function send_message_with_template_zapin($template, $post_id, $order_status, $payment_status, $phone_number) {
// Ambil data ACF berdasarkan post_id
$customer_name = get_field('customer_name', $post_id); // Nama customer
$location = get_field('locations', $post_id); // Lokasi
$unit_name = get_field('unit', $post_id); // Nama unit
$duration = get_field('duration', $post_id); // Durasi
$pickup_date = get_field('pickup_date', $post_id); // Tanggal pengambilan
$return_date = get_field('return_date', $post_id); // Tanggal pengembalian
$status_order = get_field('status-order', $post_id); // Status order
$shipping_cost = get_field('shipping_cost', $post_id); // Biaya ongkir
$unit_cost = get_field('unit_cost', $post_id); // Biaya unit
$status_payment = get_field('payment_status', $post_id); // Status pembayaran
// **Konversi Nomor Telepon**: Ganti awalan dengan '628' jika nomor dimulai dengan '8' atau '08'
if (substr($phone_number, 0, 2) === '08') {
$phone_number = '628' . substr($phone_number, 2); // Mengganti awalan '08' menjadi '628'
} elseif (substr($phone_number, 0, 1) === '8') {
$phone_number = '628' . substr($phone_number, 1); // Mengganti awalan '8' menjadi '628'
} elseif (substr($phone_number, 0, 3) !== '628') {
// Jika nomor tidak dimulai dengan '628', tambahkan '628' di depan
$phone_number = '628' . $phone_number;
}
// Tentukan pesan berdasarkan template
// Tentukan pesan berdasarkan template
$message = '';
switch ($template) {
case 'template_1': // Pesanan Baru
$message = "*Haloo Kak $customer_name di $location,* 🎉\n\n";
$message .= "Terimakasih atas pesanan Anda! Kami sangat senang bisa melayani Anda. 🙌\n\n";
$message .= "*Unit Name*: $unit_name 📦\n";
$message .= "*Durasi*: $duration ⏰\n";
$message .= "*Pengambilan*: $pickup_date 📅\n";
$message .= "*Pengembalian*: $return_date 🔄\n";
$message .= "*Status Order*: $status_order ✅\n";
$message .= "*Biaya Ongkir*: Rp " . number_format($shipping_cost, 0, ',', '.') . " 💵\n";
$message .= "*Biaya Unit*: Rp " . number_format($unit_cost, 0, ',', '.') . " 💸\n";
$message .= "*Status Pembayaran*: $status_payment 💳\n\n";
$message .= "Kami berharap kakak enjoy bermain PSnya dan Jangan ragu untuk menghubungi kami jika ada pertanyaan lebih lanjut. 😄\n\n";
$message .= "*Have fun, Kak!* 🎮✨";
break;
case 'template_2': // Reminder
$message = "*Reminder untuk Kak $customer_name di $location* ⏰\n\n";
$message .= "Mimin mau mengingatkan nih, PS-nya akan habis *1 JAM* lagi ya, kak, di *$return_date* ⏳📅\n\n";
$message .= "Jika ada pertanyaan atau butuh bantuan, termasuk jika kakak ingin menambah durasi lagi, langsung hubungi Mimin ya! 😊\n\n";
$message .= "Terima kasih sudah memilih kami! 🤙🏻";
break;
case 'template_3': // Pengembalian
$message = "*Halo Kak $customer_name,* 👋\n\n";
$message .= "Masa sewanya *sudah habis nih kak*. 🕒\n";
$message .= "Tolong dipastikan barang sudah disiapkan ya kak. 🙏\n\n";
$message .= "Terimakasih untuk sewanya, semoga kakak puas! 💖\n";
$message .= "Jika mau *order lagi*, bisa hubungi mimin yaa kak! 📱✨\n\n";
$message .= "Jangan lupa untuk *bintang 5* di google maps ya kakk😄\n\n";
$message .= "Instagram : sewapsmalang_\n";
$message .= "Maps : https://maps.app.goo.gl/TYDbJpahDiX7cj3A8";
break;
case 'template_4': // Pengembalian
$message = "*Halo Kak $customer_name,* 👋\n\n";
$message .= "Terimakasih untuk sewanya, semoga kakak puas! 💖\n";
$message .= "Jika mau *order lagi*, bisa hubungi mimin yaa kak! 📱✨\n\n";
$message .= "Jangan lupa untuk *bintang 5* di google maps ya kakk😄\n\n";
$message .= "Instagram : sewapsmalang_\n";
$message .= "Maps : https://maps.app.goo.gl/TYDbJpahDiX7cj3A8";
break;
default:
$message = "Pesan tidak dikenali.";
break;
}
// Kirim pesan menggunakan API
send_whatsapp_message_zapin($phone_number, $message);
}
// Menjadwalkan event cron untuk pengecekan status
if ( ! wp_next_scheduled( 'check_and_update_order_status' ) ) {
wp_schedule_event( time(), 'hourly', 'check_and_update_order_status' ); // Setiap jam
}
// Hook ke dalam cron job untuk mengecek status
add_action( 'check_and_update_order_status', 'check_and_update_status' );
function check_and_update_status() {
// Query untuk mencari semua post 'rental-order' yang status-nya 'Running' dan payment_status 'Sudah Bayar'
$args = array(
'post_type' => 'rental-order',
'posts_per_page' => -1, // Ambil semua post
'meta_query' => array(
array(
'key' => 'payment_status',
'value' => 'Sudah Bayar',
'compare' => '='
),
array(
'key' => 'status-order',
'value' => 'Running',
'compare' => '='
),
array(
'key' => 'return_date',
'value' => date('Y-m-d H:i:s'), // Bandingkan dengan waktu sekarang
'compare' => '<', // Mengambil semua post dengan return_date yang sudah lewat
'type' => 'DATETIME'
),
)
);
// Query WP untuk mengambil post yang relevan
$query = new WP_Query($args);
// Loop semua post dan ubah status-nya
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
$post_id = get_the_ID();
// Update status-order menjadi 'Selesai'
update_field('status-order', 'Selesai', $post_id);
error_log("Status-order untuk post ID $post_id telah diubah menjadi Selesai.");
}
wp_reset_postdata(); // Reset postdata setelah query
}
}
function get_daily_sales_expenses_report($month, $year) {
// Initialize the result array
$report_data = [];
// Loop through the days of the month (from 1 to 31)
for ($day = 1; $day <= 31; $day++) {
$day_padded = str_pad($day, 2, '0', STR_PAD_LEFT); // Format the day (e.g., 01, 02, ..., 31)
// Query for sales (income) for a specific day
$sales_args = array(
'post_type' => 'rental-order', // Sales CPT
'post_status' => 'publish',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'payment_status',
'value' => 'Sudah Bayar', // Only consider paid orders
'compare' => '='
),
),
'date_query' => array(
array(
'year' => $year,
'month' => $month,
'day' => $day,
),
),
);
$sales_query = new WP_Query($sales_args);
$total_sales = 0;
if ($sales_query->have_posts()) {
while ($sales_query->have_posts()) {
$sales_query->the_post();
$total_sales += (float) get_post_meta(get_the_ID(), 'total_cost', true); // Add total sales
}
}
wp_reset_postdata();
// Query for expenses for the specific day
$expense_args = array(
'post_type' => 'rental-expense', // Expense CPT
'post_status' => 'publish',
'posts_per_page' => -1,
'date_query' => array(
array(
'year' => $year,
'month' => $month,
'day' => $day,
),
),
);
$expense_query = new WP_Query($expense_args);
$total_expense = 0;
if ($expense_query->have_posts()) {
while ($expense_query->have_posts()) {
$expense_query->the_post();
$total_expense += (float) get_post_meta(get_the_ID(), 'cost-expense', true); // Add expense
}
}
wp_reset_postdata();
// Store the data for this day
$report_data[] = array(
'date' => "$day_padded-$month-$year",
'sales' => $total_sales,
'expenses' => $total_expense
);
}
return $report_data;
}
// Registering the shortcode
add_shortcode('daily_sales_expenses_report', function($atts) {
// Default attributes (month and year as current date)
$atts = shortcode_atts(
array(
'month' => date('m'), // Current month
'year' => date('Y'), // Current year
),
$atts,
'daily_sales_expenses_report'
);
// Fetching the report data
$report_data = get_daily_sales_expenses_report($atts['month'], $atts['year']);
// Starting the table HTML output
$output = '
';
$output .= '| Tanggal | Pemasukan | Pengeluaran |
';
$output .= '';
// Loop through the report data and populate the table rows
foreach ($report_data as $data) {
$output .= '';
$output .= '| ' . $data['date'] . ' | ';
$output .= 'Rp ' . number_format($data['sales'], 0, ',', '.') . ' | ';
$output .= 'Rp ' . number_format($data['expenses'], 0, ',', '.') . ' | ';
$output .= '
';
}
$output .= '';
$output .= '
';
return $output; // Return the table HTML
});
function get_daily_sales_expenses() {
$month = date('m'); // Current month
$year = date('Y'); // Current year
$report_data = [];
// Loop through the days of the month (1 to 31)
for ($day = 1; $day <= 31; $day++) {
$day_padded = str_pad($day, 2, '0', STR_PAD_LEFT); // Format day (01, 02, ..., 31)
// Query for sales data for this day
$sales_query = new WP_Query([
'post_type' => 'rental-order',
'posts_per_page' => -1,
'meta_query' => [
[
'key' => 'payment_status',
'value' => 'Sudah Bayar',
'compare' => '='
]
],
'date_query' => [
[
'year' => $year,
'month' => $month,
'day' => $day,
]
]
]);
$total_sales = 0;
while ($sales_query->have_posts()) {
$sales_query->the_post();
$total_sales += (float) get_post_meta(get_the_ID(), 'total_cost', true);
}
wp_reset_postdata();
// Query for expenses data for this day
$expense_query = new WP_Query([
'post_type' => 'rental-expense',
'posts_per_page' => -1,
'date_query' => [
[
'year' => $year,
'month' => $month,
'day' => $day,
]
]
]);
$total_expense = 0;
while ($expense_query->have_posts()) {
$expense_query->the_post();
$total_expense += (float) get_post_meta(get_the_ID(), 'cost-expense', true);
}
wp_reset_postdata();
// Combine sales and expenses for the given day
$report_data[] = [
'date' => "$day_padded-$month-$year",
'sales' => $total_sales,
'expenses' => $total_expense
];
}
// Generate HTML output for the table rows
$output = '';
foreach ($report_data as $index => $data) {
$output .= '';
$output .= '| ' . ($index + 1) . ' | ';
$output .= '' . $data['date'] . ' | ';
$output .= '' . 'Rp ' . number_format($data['expenses'], 0, ',', '.') . ' | ';
$output .= '' . 'Rp ' . number_format($data['sales'], 0, ',', '.') . ' | ';
$output .= '
';
}
return $output;
}
add_shortcode('daily_sales_expenses', 'get_daily_sales_expenses');
function restrict_content_to_logged_in_users() {
// Check if user is not logged in
if (!is_user_logged_in() && !is_page('login')) {
// Redirect to the login page
wp_redirect('/login/');
exit();
}
}
add_action('template_redirect', 'restrict_content_to_logged_in_users');
// Mendaftarkan endpoint API untuk update stok
add_action('rest_api_init', function() {
register_rest_route('websee/v1', '/update-stock', array(
'methods' => 'POST',
'callback' => 'websee_handle_update_stock', // Handler baru
'permission_callback' => '__return_true',
));
});
// Fungsi untuk menangani permintaan update stok
function websee_handle_update_stock(WP_REST_Request $request) {
$data = $request->get_json_params();
error_log('Update stock received: ' . print_r($data, true));
if (isset($data['stoku_ps3']) && isset($data['stoku_ps4']) && isset($data['stoku_tv']) &&
isset($data['stoku_ps3-mines']) && isset($data['stoku_ps4-mines']) && isset($data['stoku_tv-mines'])) {
websee_increase_stock('ps3', intval($data['stoku_ps3']));
websee_increase_stock('ps4', intval($data['stoku_ps4']));
websee_increase_stock('tv', intval($data['stoku_tv']));
websee_decrease_stock('ps3', intval($data['stoku_ps3-mines']));
websee_decrease_stock('ps4', intval($data['stoku_ps4-mines']));
websee_decrease_stock('tv', intval($data['stoku_tv-mines']));
return new WP_REST_Response('Stock updated successfully', 200);
}
error_log('Invalid data received: ' . print_r($data, true));
return new WP_REST_Response('Invalid data', 400);
}
// Fungsi untuk mengurangi stok produk
function websee_decrease_stock($product, $quantity) {
$field_name = "stock_$product";
$current_stock = (int) get_field($field_name, 'option');
$new_stock = $current_stock - $quantity;
if ($new_stock < 0) {
$new_stock = 0;
}
update_field($field_name, $new_stock, 'option');
error_log("✅ Stok $product berkurang menjadi $new_stock.");
}
// Fungsi untuk menambah stok produk
function websee_increase_stock($product, $quantity) {
$field_name = "stock_$product";
$current_stock = (int) get_field($field_name, 'option');
$new_stock = $current_stock + $quantity;
update_field($field_name, $new_stock, 'option');
error_log("✅ Stok $product bertambah menjadi $new_stock.");
}
// Disable the WordPress admin bar for all users
add_filter('show_admin_bar', '__return_false');
Saber – Laman 13
Skip to main content
Riwayat Pemesanan
| No | Nama | Lokasi | Tanggal Pengambilan | Unit | Durasi | Harga | Status Unit | Tanggal Pengembalian | Status Pembayaran | Ongkir |
|---|
| | | | | | | | | | |
|---|
| 121 | Fikri | | 30-05-2026, 19:34 | PS4 | 1 Hari | 75000 |
| 31-05-2026, 19:34 |
| 15000 |
| 122 | Fifi | | 30-05-2026, 17:32 | PS4 | 2 Hari | 150000 |
| 01-06-2026, 17:32 |
| 35000 |
| 123 | Maya Wijayanti | | 30-05-2026, 13:59 | PS3 | 1 Hari | 45000 |
| 31-05-2026, 13:59 |
| |
| 124 | Damar Radiktya | | 30-05-2026, 11:25 | PS4 | 1 Hari | 75000 |
| 31-05-2026, 11:25 |
| |
| 125 | Mitra Dayu | | 29-05-2026, 23:40 | PS4 | 1 Hari | 75000 |
| 30-05-2026, 23:40 |
| 30000 |
| 126 | Titis | | 01-06-2026, 03:02 | PS4TV | 1 Hari | 100000 |
| 02-06-2026, 03:02 |
| 35000 |
| 127 | Winona | | 29-05-2026, 21:04 | PS4TV | 1 Hari | 100000 |
| 30-05-2026, 21:04 |
| 10000 |
| 128 | Yohanes | | 29-05-2026, 17:01 | PS4TV | 1 Hari | 100000 |
| 30-05-2026, 17:01 |
| |
| 129 | Hanif | Karangploso | 28-05-2026, 21:40 | PS4TV | 1 Hari | 100000 |
| 29-05-2026, 21:40 |
| 35000 |
| 130 | Fian | | 31-05-2026, 17:33 | PS3 | 1 Hari | 45000 |
| 01-06-2026, 17:33 |
| 35000 |
|
[frontend_indexing_buttons]