diff --git a/.gitignore b/.gitignore index e9361f19..3bd975cb 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ docs/** !docs/insomnia.rest.json !system/uploads/paid.png system/uploads/invoices/** +!system/uploads/invoices/ +!system/uploads/invoices/index.html \ No newline at end of file diff --git a/system/autoload/Invoice.php b/system/autoload/Invoice.php index 96f558e6..8215559b 100644 --- a/system/autoload/Invoice.php +++ b/system/autoload/Invoice.php @@ -8,12 +8,12 @@ class Invoice { try { if (empty($invoiceData['invoice'])) { - throw new Exception("Invoice ID is required"); + throw new Exception(Lang::T("Invoice No is required")); } $template = Lang::getNotifText('email_invoice'); if (!$template) { - throw new Exception("Invoice template not found"); + throw new Exception(Lang::T("Invoice template not found")); } if (strpos($template, 'Output($outputPath, 'F'); if (!file_exists($outputPath)) { - throw new Exception("Failed to save PDF file"); + throw new Exception(Lang::T("Failed to save PDF file")); } return $filename; @@ -67,7 +71,7 @@ class Invoice return preg_replace_callback('/\[\[(\w+)\]\]/', function ($matches) use ($invoiceData) { $key = $matches[1]; if (!isset($invoiceData[$key])) { - _log("Missing invoice key: $key"); + _log(Lang::T("Missing invoice key: ") . $key); return ''; } @@ -80,92 +84,84 @@ class Invoice } if ($key === 'bill_rows') { - return html_entity_decode($invoiceData[$key]); + return $invoiceData[$key]; } - return htmlspecialchars($invoiceData[$key] ?? ''); }, $template); } - public static function sendInvoice($userId, $status = "Unpaid") + /** + * Send invoice to user + * + * @param int $userId + * @param array $invoice + * @param array $bills + * @param string $status + * @param string $invoiceNo + * @return bool + */ + + public static function sendInvoice($userId, $invoice = null, $bills = [], $status = "Unpaid", $invoiceNo = null) { global $config, $root_path, $UPLOAD_PATH; - if (empty($config['currency_code'])) { - $config['currency_code'] = '$'; - } + // Set default currency code + $config['currency_code'] ??= '$'; $account = ORM::for_table('tbl_customers')->find_one($userId); - - if (!$account) { - _log("Failed to send invoice: User not found"); - sendTelegram("Failed to send invoice: User not found"); - return false; + self::validateAccount($account); + if (!$invoiceNo) { + $invoiceNo = "INV-" . Package::_raid(); } + // Fetch invoice if not provided + if ($status === "Unpaid" && !$invoice) { + $data = ORM::for_table('tbl_user_recharges')->where('customer_id', $userId) + ->where('status', 'off') + ->left_outer_join('tbl_plans', 'tbl_user_recharges.namebp = tbl_plans.name_plan') + ->select('tbl_plans.price', 'price') + ->select('tbl_plans.name_plan', 'namebp') + ->find_one(); + if (!$data) { + $data = ORM::for_table('tbl_user_recharges')->where('username', $account->username) + ->left_outer_join('tbl_plans', 'tbl_user_recharges.namebp = tbl_plans.name_plan') + ->select('tbl_plans.price', 'price') + ->select('tbl_plans.name_plan', 'namebp') + ->where('status', 'off') + ->find_one(); + } + if (!$data) { + throw new Exception(Lang::T("No unpaid invoice found for username:") . $account->username); + } + $invoice = [ + 'price' => $data->price, + 'plan_name' => $data->namebp, + 'routers' => $data->routers, + ]; - $invoice = ORM::for_table("tbl_transactions")->where("username", $account->username)->find_one(); - + } else if ($status === "Paid" && !$invoice) { + $invoice = ORM::for_table("tbl_transactions")->where("username", $account->username)->find_one(); + } if (!$invoice) { - _log("Failed to send invoice: Transaction not found"); - sendTelegram("Failed to send invoice: Transaction not found"); - return false; + throw new Exception(Lang::T("Transaction not found for username: ") . $account->username); } - [$additionalBills, $add_cost] = User::getBills($account->id); - - $invoiceItems = [ - [ - 'description' => $invoice->plan_name, - 'details' => 'Monthly Subscription', - 'amount' => (float) $invoice->price - ] - ]; - $subtotal = (float) $invoice->price; - - if ($add_cost > 0 && $invoice->routers != 'balance') { - foreach ($additionalBills as $description => $amount) { - if (is_numeric($amount)) { - $invoiceItems[] = [ - 'description' => $description, - 'details' => 'Additional Bill', - 'amount' => (float) $amount - ]; - $subtotal += (float) $amount; - } else { - _log("Invalid bill amount for {$description}: {$amount}"); - } - } + // Get additional bills if not provided + if (empty($bills)) { + [$bills, $add_cost] = User::getBills($account->id); } - $tax_rate = (float) ($config['tax_rate'] ?? 0); + $invoiceItems = self::generateInvoiceItems($invoice, $bills, $add_cost); + $subtotal = array_sum(array_column($invoiceItems, 'amount')); $tax = $config['enable_tax'] ? Package::tax($subtotal) : 0; - $total = ($tax > 0) ? $subtotal + $tax : $subtotal + $tax; + $tax_rate = $config['tax_rate'] ?? 0; + $total = $subtotal + $tax; - $token = User::generateToken($account->id, 1); - if (!empty($token['token'])) { - $tur = ORM::for_table('tbl_user_recharges') - ->where('customer_id', $account->id) - ->where('namebp', $invoice->plan_name); + $payLink = self::generatePaymentLink($account, $invoice, $status); + $logo = self::getCompanyLogo($UPLOAD_PATH, $root_path); - switch ($status) { - case 'Paid': - $tur->where('status', 'on'); - break; - default: - $tur->where('status', 'off'); - break; - } - $turResult = $tur->find_one(); - $payLink = $turResult ? '?_route=home&recharge=' . $turResult['id'] . '&uid=' . urlencode($token['token']) : '?_route=home'; - } else { - $payLink = '?_route=home'; - } - - $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); - $logo = (file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png')) ? $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.png?' . time() : $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.default.png'; $invoiceData = [ - 'invoice' => "INV-" . Package::_raid(), + 'invoice' => $invoiceNo, 'fullname' => $account->fullname, 'email' => $account->email, 'address' => $account->address, @@ -182,31 +178,90 @@ class Invoice 'payment_link' => $payLink ]; - if (!isset($invoiceData['bill_rows']) || empty($invoiceData['bill_rows'])) { - _log("Invoice Error: Bill rows data is empty."); + if (empty($invoiceData['bill_rows'])) { + throw new Exception(Lang::T("Bill rows data is empty.")); } $filename = self::generateInvoice($invoiceData); - - if ($filename) { - $pdfPath = "system/uploads/invoices/{$filename}"; - - try { - Message::sendEmail( - $account->email, - "Invoice for Account {$account->fullname}", - "Please find your invoice attached", - $pdfPath - ); - return true; - } catch (\Exception $e) { - _log("Failed to send invoice email: " . $e->getMessage()); - sendTelegram("Failed to send invoice email: " . $e->getMessage()); - return false; - } + if (!$filename) { + throw new Exception(Lang::T("Failed to generate invoice PDF")); } - return false; + $pdfPath = "system/uploads/invoices/{$filename}"; + self::saveToDatabase($filename, $account->id, $invoiceData, $total); + + try { + Message::sendEmail( + $account->email, + Lang::T("Invoice for Account {$account->fullname}"), + Lang::T("Please find your invoice attached"), + $pdfPath + ); + return true; + } catch (\Exception $e) { + throw new Exception(Lang::T("Failed to send email invoice to ") . $account->email . ". " . Lang::T("Reason: ") . $e->getMessage()); + } + } + + private static function validateAccount($account) + { + if (!$account) { + throw new Exception(Lang::T("User not found")); + } + if (!$account->email || !filter_var($account->email, FILTER_VALIDATE_EMAIL)) { + throw new Exception(Lang::T("Invalid user email")); + } + } + + private static function generateInvoiceItems($invoice, $bills, $add_cost) + { + $items = [ + [ + 'description' => $invoice['plan_name'], + 'details' => Lang::T('Subscription'), + 'amount' => (float) $invoice['price'] + ] + ]; + + if ($invoice->routers != 'balance') { + foreach ($bills as $description => $amount) { + if (is_numeric($amount)) { + $items[] = [ + 'description' => $description, + 'details' => Lang::T('Additional Bill'), + 'amount' => (float) $amount + ]; + } else { + _log(Lang::T("Invalid bill amount for {$description}: {$amount}")); + } + } + } + return $items; + } + + private static function generatePaymentLink($account, $invoice, $status) + { + $token = User::generateToken($account->id, 1); + if (empty($token['token'])) { + return '?_route=home'; + } + + $tur = ORM::for_table('tbl_user_recharges') + ->where('customer_id', $account->id) + ->where('namebp', $invoice->plan_name); + + $tur->where('status', $status === 'Paid' ? 'on' : 'off'); + $turResult = $tur->find_one(); + + return $turResult ? '?_route=home&recharge=' . $turResult['id'] . '&uid=' . urlencode($token['token']) : '?_route=home'; + } + + private static function getCompanyLogo($UPLOAD_PATH, $root_path) + { + $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); + return file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png') ? + $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.png?' . time() : + $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.default.png'; } private static function generateBillRows($items, $currency, $subtotal, $tax_rate, $tax, $total) @@ -222,9 +277,11 @@ class Invoice "; foreach ($items as $item) { + $desc = htmlspecialchars($item['description'], ENT_QUOTES); + $details = htmlspecialchars($item['details'], ENT_QUOTES); $html .= " - {$item['description']} - {$item['details']} + {$desc} + {$details} {$currency}" . number_format((float) $item['amount'], 2) . " "; } @@ -247,6 +304,44 @@ class Invoice return $html; } + private static function saveToDatabase($filename, $customer_id, $invoiceData, $total) + { + $invoice = ORM::for_table('tbl_invoices')->create(); + $invoice->number = $invoiceData['invoice']; + $invoice->customer_id = $customer_id; + $invoice->fullname = $invoiceData['fullname']; + $invoice->email = $invoiceData['email']; + $invoice->address = $invoiceData['address']; + $invoice->status = $invoiceData['status']; + $invoice->due_date = $invoiceData['due_date']; + $invoice->filename = $filename; + $invoice->amount = $total; + $invoice->data = json_encode($invoiceData); + $invoice->created_at = date('Y-m-d H:i:s'); + $invoice->save(); + return $invoice->id; + } + public static function getAll() + { + return ORM::for_table('tbl_invoices')->order_by_desc('id')->find_many(); + } + public static function getById($id) + { + return ORM::for_table('tbl_invoices')->find_one($id); + } + public static function getByNumber($number) + { + return ORM::for_table('tbl_invoices')->where('number', $number)->find_one(); + } + public static function delete($id) + { + $invoice = ORM::for_table('tbl_invoices')->find_one($id); + if ($invoice) { + $invoice->delete(); + return true; + } + return false; + } } diff --git a/system/autoload/Message.php b/system/autoload/Message.php index 725d8f39..282a4513 100644 --- a/system/autoload/Message.php +++ b/system/autoload/Message.php @@ -46,7 +46,7 @@ class Message $txts = str_split($txt, 160); try { foreach ($txts as $txt) { - self::sendSMS($config['sms_url'], $phone, $txt); + self::sendSMS($phone, $txt); self::logMessage('SMS', $phone, $txt, 'Success'); } } catch (Throwable $e) { @@ -140,6 +140,7 @@ class Message } mail($to, $subject, $body, $attr); self::logMessage('Email', $to, $body, 'Success'); + return true; } else { $mail = new PHPMailer(); $mail->isSMTP(); @@ -188,8 +189,10 @@ class Message if (!$mail->send()) { $errorMessage = Lang::T("Email not sent, Mailer Error: ") . $mail->ErrorInfo; self::logMessage('Email', $to, $body, 'Error', $errorMessage); + return false; } else { self::logMessage('Email', $to, $body, 'Success'); + return true; } //

@@ -396,9 +399,11 @@ class Message $v->body = nl2br($body); $v->save(); self::logMessage("Inbox", $user->username, $body, "Success"); + return true; } catch (Throwable $e) { $errorMessage = Lang::T("Error adding message to inbox: " . $e->getMessage()); self::logMessage('Inbox', $user->username, $body, 'Error', $errorMessage); + return false; } } diff --git a/system/controllers/export.php b/system/controllers/export.php index 154d188f..a43a4d70 100644 --- a/system/controllers/export.php +++ b/system/controllers/export.php @@ -71,8 +71,8 @@ switch ($action) { if (count($plns) > 0) { $query->where_in('plan_name', $plns); } - $x = $query->find_array(); - $xy = $query->sum('price'); + $x = $query->find_array(); + $xy = $query->sum('price'); $ui->assign('sd', $sd); $ui->assign('ed', $ed); @@ -114,7 +114,10 @@ switch ($action) { $query = ORM::for_table('tbl_transactions') ->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) >= " . strtotime("$sd $ts")) ->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te")) - ->order_by_desc('id'); + ->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username') + ->select('tbl_transactions.*') + ->select('tbl_customers.fullname', 'fullname') + ->order_by_desc('tbl_transactions.id'); if (count($tps) > 0) { $query->where_in('type', $tps); } @@ -131,13 +134,13 @@ switch ($action) { if (count($plns) > 0) { $query->where_in('plan_name', $plns); } - $x = $query->find_array(); - $xy = $query->sum('price'); + $x = $query->find_array(); + $xy = $query->sum('price'); $title = ' Reports [' . $mdate . ']'; $title = str_replace('-', ' ', $title); - $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); + $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); if (file_exists($UPLOAD_PATH . '/logo.png')) { $logo = $UPLOAD_URL_PATH . '/logo.png'; } else { @@ -154,10 +157,11 @@ switch ($action) {

- + + @@ -170,6 +174,7 @@ switch ($action) { foreach ($x as $value) { $username = $value['username']; + $fullname = $value['fullname']; $plan_name = $value['plan_name']; $type = $value['type']; $price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']); @@ -181,6 +186,7 @@ switch ($action) { $html .= "" . " + @@ -245,7 +251,7 @@ $style $html EOF; $mpdf->WriteHTML($nhtml); - $mpdf->Output('phpnuxbill_reports_'.date('Ymd_His') . '.pdf', 'D'); + $mpdf->Output('phpnuxbill_reports_' . date('Ymd_His') . '.pdf', 'D'); } else { echo 'No Data'; } @@ -258,13 +264,17 @@ EOF; $stype = _post('stype'); $d = ORM::for_table('tbl_transactions'); + $d->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username') + ->select('tbl_transactions.*') + ->select('tbl_customers.fullname', 'fullname') + ->order_by_desc('tbl_transactions.id'); if ($stype != '') { $d->where('type', $stype); } $d->where_gte('recharged_on', $fdate); $d->where_lte('recharged_on', $tdate); $d->order_by_desc('id'); - $x = $d->find_many(); + $x = $d->find_many(); $dr = ORM::for_table('tbl_transactions'); if ($stype != '') { @@ -290,6 +300,10 @@ EOF; $tdate = _post('tdate'); $stype = _post('stype'); $d = ORM::for_table('tbl_transactions'); + $d->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username') + ->select('tbl_transactions.*') + ->select('tbl_customers.fullname', 'fullname') + ->order_by_desc('tbl_transactions.id'); if ($stype != '') { $d->where('type', $stype); } @@ -297,7 +311,7 @@ EOF; $d->where_gte('recharged_on', $fdate); $d->where_lte('recharged_on', $tdate); $d->order_by_desc('id'); - $x = $d->find_many(); + $x = $d->find_many(); $dr = ORM::for_table('tbl_transactions'); if ($stype != '') { @@ -311,7 +325,7 @@ EOF; $title = ' Reports [' . $mdate . ']'; $title = str_replace('-', ' ', $title); - $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); + $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); if (file_exists($UPLOAD_PATH . '/logo.png')) { $logo = $UPLOAD_URL_PATH . '/logo.png'; } else { @@ -332,6 +346,7 @@ EOF;
' . Lang::T('Username') . '' . Lang::T('Fullname') . ' ' . Lang::T('Plan Name') . ' ' . Lang::T('Type') . ' ' . Lang::T('Plan Price') . '$username$fullname $plan_name $type $price
+ @@ -344,6 +359,7 @@ EOF; foreach ($x as $value) { $username = $value['username']; + $fullname = $value['fullname']; $plan_name = $value['plan_name']; $type = $value['type']; $price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']); @@ -355,7 +371,8 @@ EOF; $html .= "" . " - + + diff --git a/system/controllers/invoices.php b/system/controllers/invoices.php new file mode 100644 index 00000000..e072468c --- /dev/null +++ b/system/controllers/invoices.php @@ -0,0 +1,27 @@ +assign('_title', Lang::T('Invoice Lists')); +$ui->assign('_system_menu', 'reports'); +$action = $routes['1']; +$ui->assign('_admin', $admin); + +if (empty($action)) { + $action = 'list'; +} +switch ($action) { + case 'list': + $ui->assign('xheader', ''); + $ui->assign('invoices', Invoice::getAll()); + $ui->display('admin/invoices/list.tpl'); + break; + default: + $ui->display('admin/404.tpl'); +} diff --git a/system/controllers/message.php b/system/controllers/message.php index b2df636d..a1407698 100644 --- a/system/controllers/message.php +++ b/system/controllers/message.php @@ -57,56 +57,79 @@ EOT; _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); } - // Get form data - $id_customer = $_POST['id_customer']; - $message = $_POST['message']; - $via = $_POST['via']; + $id_customer = $_POST['id_customer'] ?? ''; + $message = $_POST['message'] ?? ''; + $via = $_POST['via'] ?? ''; + $subject = $_POST['subject'] ?? ''; - // Check if fields are empty - if ($id_customer == '' or $message == '' or $via == '') { - r2(getUrl('message/send'), 'e', Lang::T('All field is required')); - } else { - // Get customer details from the database - $c = ORM::for_table('tbl_customers')->find_one($id_customer); + // Validate subject based on the selected channel + if (($via === 'all' || $via === 'email' || $via === 'inbox') && empty($subject)) { + r2(getUrl('message/send'), 'e', LANG::T('Subject is required to send message using') . ' ' . $via . '.'); + } - // Replace placeholders in the message with actual values - $message = str_replace('[[name]]', $c['fullname'], $message); - $message = str_replace('[[user_name]]', $c['username'], $message); - $message = str_replace('[[phone]]', $c['phonenumber'], $message); - $message = str_replace('[[company_name]]', $config['CompanyName'], $message); - if (strpos($message, '[[payment_link]]') !== false) { - // token only valid for 1 day, for security reason - $token = User::generateToken($c['id'], 1); - if (!empty($token['token'])) { - $tur = ORM::for_table('tbl_user_recharges') - ->where('customer_id', $c['id']) - //->where('namebp', $package) - ->find_one(); - if ($tur) { - $url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']); - $message = str_replace('[[payment_link]]', $url, $message); - } - } else { - $message = str_replace('[[payment_link]]', '', $message); + if (empty($id_customer) || empty($message) || empty($via)) { + r2(getUrl('message/send'), 'e', Lang::T('Customer, Message, and Channel are required')); + } + + $customer = ORM::for_table('tbl_customers')->find_one($id_customer); + if (!$customer) { + r2(getUrl('message/send'), 'e', Lang::T('Customer not found')); + } + + // Replace placeholders in message and subject + $currentMessage = str_replace( + ['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'], + [$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']], + $message + ); + + $currentSubject = str_replace( + ['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'], + [$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']], + $subject + ); + + if (strpos($message, '[[payment_link]]') !== false) { + $token = User::generateToken($customer['id'], 1); + if (!empty($token['token'])) { + $tur = ORM::for_table('tbl_user_recharges') + ->where('customer_id', $customer['id']) + ->find_one(); + if ($tur) { + $url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']); + $currentMessage = str_replace('[[payment_link]]', $url, $currentMessage); } - } - - - //Send the message - if ($via == 'sms' || $via == 'both') { - $smsSent = Message::sendSMS($c['phonenumber'], $message); - } - - if ($via == 'wa' || $via == 'both') { - $waSent = Message::sendWhatsapp($c['phonenumber'], $message); - } - - if (isset($smsSent) || isset($waSent)) { - r2(getUrl('message/send'), 's', Lang::T('Message Sent Successfully')); } else { - r2(getUrl('message/send'), 'e', Lang::T('Failed to send message')); + $currentMessage = str_replace('[[payment_link]]', '', $currentMessage); } } + + // Send the message through the selected channels + $smsSent = $waSent = $emailSent = $inboxSent = false; + + if ($via === 'sms' || $via === 'both' || $via === 'all') { + $smsSent = Message::sendSMS($customer['phonenumber'], $currentSubject); + } + + if ($via === 'wa' || $via === 'both' || $via === 'all') { + $waSent = Message::sendWhatsapp($customer['phonenumber'], $currentSubject); + } + + if ($via === 'email' || $via === 'all') { + $emailSent = Message::sendEmail($customer['email'], $currentSubject, $currentMessage); + } + + if ($via === 'inbox' || $via === 'all') { + $inboxSent = Message::addToInbox($customer['id'], $currentSubject, $currentMessage, 'Admin'); + } + + // Check if any message was sent successfully + if ($smsSent || $waSent || $emailSent || $inboxSent) { + r2(getUrl('message/send'), 's', Lang::T('Message Sent Successfully')); + } else { + r2(getUrl('message/send'), 'e', Lang::T('Failed to send message')); + } + break; case 'send_bulk': @@ -133,11 +156,16 @@ EOT; $batch = $_REQUEST['batch'] ?? 100; $page = $_REQUEST['page'] ?? 0; $router = $_REQUEST['router'] ?? null; - $test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on' ? true : false; + $test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on'; $service = $_REQUEST['service'] ?? ''; + $subject = $_REQUEST['subject'] ?? ''; if (empty($group) || empty($message) || empty($via) || empty($service)) { - die(json_encode(['status' => 'error', 'message' => 'All fields are required'])); + die(json_encode(['status' => 'error', 'message' => LANG::T('All fields are required')])); + } + + if (in_array($via, ['all', 'email', 'inbox']) && empty($subject)) { + die(json_encode(['status' => 'error', 'message' => LANG::T('Subject is required to send message using') . ' ' . $via . '.'])); } // Get batch of customers based on group @@ -153,7 +181,7 @@ EOT; default: $router = ORM::for_table('tbl_routers')->find_one($router); if (!$router) { - die(json_encode(['status' => 'error', 'message' => 'Invalid router'])); + die(json_encode(['status' => 'error', 'message' => LANG::T('Invalid router')])); } $routerName = $router->name; break; @@ -200,6 +228,9 @@ EOT; ['tbl_customers.phonenumber', 'phonenumber'], ['tbl_user_recharges.customer_id', 'customer_id'], ['tbl_customers.fullname', 'fullname'], + ['tbl_customers.username', 'username'], + ['tbl_customers.email', 'email'], + ['tbl_customers.service_type', 'service_type'], ]); $customers = $query->find_array(); } else { @@ -287,7 +318,13 @@ EOT; $totalSMSFailed = 0; $totalWhatsappSent = 0; $totalWhatsappFailed = 0; + $totalEmailSent = 0; + $totalEmailFailed = 0; + $totalInboxSent = 0; + $totalInboxFailed = 0; $batchStatus = []; + //$subject = $config['CompanyName'] . ' ' . Lang::T('Notification Message'); + $form = 'Admin'; foreach ($customers as $customer) { $currentMessage = str_replace( @@ -296,6 +333,12 @@ EOT; $message ); + $currentSubject = str_replace( + ['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'], + [$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']], + $subject + ); + $phoneNumber = preg_replace('/\D/', '', $customer['phonenumber']); if (empty($phoneNumber)) { @@ -310,14 +353,14 @@ EOT; if ($test) { $batchStatus[] = [ 'name' => $customer['fullname'], - 'phone' => $customer['phonenumber'], + 'channel' => 'Test Channel', 'status' => 'Test Mode', 'message' => $currentMessage, 'service' => $service, 'router' => $routerName, ]; } else { - if ($via == 'sms' || $via == 'both') { + if ($via === 'sms' || $via === 'both' || $via === 'all') { if (Message::sendSMS($customer['phonenumber'], $currentMessage)) { $totalSMSSent++; $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'SMS Sent', 'message' => $currentMessage]; @@ -327,13 +370,33 @@ EOT; } } - if ($via == 'wa' || $via == 'both') { + if ($via === 'wa' || $via == 'both' || $via === 'all') { if (Message::sendWhatsapp($customer['phonenumber'], $currentMessage)) { $totalWhatsappSent++; - $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage]; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage]; } else { $totalWhatsappFailed++; - $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage]; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage]; + } + } + + if ($via === 'email' || $via === 'all') { + if (Message::sendEmail($customer['email'], $currentSubject, $currentMessage)) { + $totalEmailSent++; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['email'], 'status' => 'Email Sent', 'message' => $currentMessage]; + } else { + $totalEmailFailed++; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['email'], 'status' => 'Email Failed', 'message' => $currentMessage]; + } + } + + if ($via === 'inbox' || $via === 'all') { + if (Message::addToInbox($customer['customer_id'], $currentSubject, $currentMessage, $form)) { + $totalInboxSent++; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => 'Inbox', 'status' => 'Inbox Message Sent', 'message' => $currentMessage]; + } else { + $totalInboxFailed++; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => 'Inbox', 'status' => 'Inbox Message Failed', 'message' => $currentMessage]; } } } @@ -348,8 +411,8 @@ EOT; 'page' => $page + 1, 'batchStatus' => $batchStatus, 'message' => $currentMessage, - 'totalSent' => $totalSMSSent + $totalWhatsappSent, - 'totalFailed' => $totalSMSFailed + $totalWhatsappFailed, + 'totalSent' => $totalSMSSent + $totalWhatsappSent + $totalEmailSent + $totalInboxSent, + 'totalFailed' => $totalSMSFailed + $totalWhatsappFailed + $totalEmailFailed + $totalInboxFailed, 'hasMore' => $hasMore, 'service' => $service, 'router' => $routerName, @@ -365,16 +428,20 @@ EOT; // Get the posted data $customerIds = $_POST['customer_ids'] ?? []; $via = $_POST['message_type'] ?? ''; + $subject = $_POST['subject'] ?? ''; $message = isset($_POST['message']) ? trim($_POST['message']) : ''; if (empty($customerIds) || empty($message) || empty($via)) { echo json_encode(['status' => 'error', 'message' => Lang::T('Invalid customer IDs, Message, or Message Type.')]); exit; } + if ($via === 'all' || $via === 'email' || $via === 'inbox' && empty($subject)) { + die(json_encode(['status' => 'error', 'message' => LANG::T('Subject is required to send message using') . ' ' . $via . '.'])); + } + // Prepare to send messages $sentCount = 0; $failedCount = 0; - $subject = Lang::T('Notification Message'); $form = 'Admin'; foreach ($customerIds as $customerId) { diff --git a/system/controllers/reports.php b/system/controllers/reports.php index b24ce39c..e18e1f87 100644 --- a/system/controllers/reports.php +++ b/system/controllers/reports.php @@ -58,7 +58,7 @@ switch ($action) { $w = []; $v = []; foreach ($mts as $mt) { - $w[] ='method'; + $w[] = 'method'; $v[] = "$mt - %"; } $query->where_likes($w, $v); @@ -91,7 +91,7 @@ switch ($action) { $w = []; $v = []; foreach ($mts as $mt) { - $w[] ='method'; + $w[] = 'method'; $v[] = "$mt - %"; } $query->where_likes($w, $v); @@ -161,7 +161,7 @@ switch ($action) { $w = []; $v = []; foreach ($mts as $mt) { - $w[] ='method'; + $w[] = 'method'; $v[] = "$mt - %"; } $query->where_likes($w, $v); @@ -246,7 +246,7 @@ switch ($action) { $total += $v; $array['data'][] = $v; } - if($total>0){ + if ($total > 0) { $result['datas'][] = $array; } } @@ -258,18 +258,29 @@ switch ($action) { die(); case 'by-date': case 'activation': - $q = (_post('q') ? _post('q') : _get('q')); + $q = trim(_post('q') ?: _get('q')); $keep = _post('keep'); + if (!empty($keep)) { - ORM::raw_execute("DELETE FROM tbl_transactions WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))"); - r2(getUrl('logs/list/'), 's', "Delete logs older than $keep days"); + ORM::raw_execute("DELETE FROM tbl_transactions WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL ? DAY))", [$keep]); + r2(getUrl('reports/activation/'), 's', "Deleted logs older than $keep days"); } - if ($q != '') { - $query = ORM::for_table('tbl_transactions')->where_like('invoice', '%' . $q . '%')->order_by_desc('id'); + + $query = ORM::for_table('tbl_transactions') + ->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username') + ->select('tbl_transactions.*') + ->select('tbl_customers.fullname', 'fullname') + ->order_by_desc('tbl_transactions.id'); + + if ($q !== '') { + $query->where_like('invoice', "%$q%"); + } + + try { $d = Paginator::findMany($query, ['q' => $q]); - } else { - $query = ORM::for_table('tbl_transactions')->order_by_desc('id'); - $d = Paginator::findMany($query); + } catch (Exception $e) { + r2(getUrl('reports/activation/'), 'e', 'Database query failed: ' . $e->getMessage()); + $d = []; } $ui->assign('activation', $d); @@ -291,6 +302,10 @@ switch ($action) { $stype = _post('stype'); $d = ORM::for_table('tbl_transactions'); + $d->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username') + ->select('tbl_transactions.*') + ->select('tbl_customers.fullname', 'fullname') + ->order_by_desc('tbl_transactions.id'); if ($stype != '') { $d->where('type', $stype); } @@ -298,7 +313,7 @@ switch ($action) { $d->where_gte('recharged_on', $fdate); $d->where_lte('recharged_on', $tdate); $d->order_by_desc('id'); - $x = $d->find_many(); + $x = $d->find_many(); $dr = ORM::for_table('tbl_transactions'); if ($stype != '') { @@ -348,7 +363,10 @@ switch ($action) { $query = ORM::for_table('tbl_transactions') ->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) >= " . strtotime("$sd $ts")) ->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te")) - ->order_by_desc('id'); + ->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username') + ->select('tbl_transactions.*') + ->select('tbl_customers.fullname', 'fullname') + ->order_by_desc('tbl_transactions.id'); if (count($tps) > 0) { $query->where_in('type', $tps); } @@ -356,7 +374,7 @@ switch ($action) { $w = []; $v = []; foreach ($mts as $mt) { - $w[] ='method'; + $w[] = 'method'; $v[] = "$mt - %"; } $query->where_likes($w, $v); diff --git a/system/controllers/settings.php b/system/controllers/settings.php index a1599e8b..4366a32f 100644 --- a/system/controllers/settings.php +++ b/system/controllers/settings.php @@ -146,7 +146,8 @@ switch ($action) { $r = ORM::for_table('tbl_routers')->find_many(); $ui->assign('r', $r); if (function_exists("shell_exec")) { - $php = trim(shell_exec('which php')); + $which = stripos(php_uname('s'), "Win") === 0 ? 'where' : 'which'; + $php = trim(shell_exec("$which php")); if (empty($php)) { $php = 'php'; } diff --git a/system/cron_reminder.php b/system/cron_reminder.php index ad4adbca..01092c80 100644 --- a/system/cron_reminder.php +++ b/system/cron_reminder.php @@ -49,7 +49,7 @@ foreach ($d as $ds) { } else { $price = $p['price']; } - if ($ds['expiration'] == $day7 && $config['notification_reminder_7day'] !== 'no') { + if ($ds['expiration'] == $day7 && $config['notification_reminder_7days'] !== 'no') { try { echo Message::sendPackageNotification( $c, @@ -61,7 +61,7 @@ foreach ($d as $ds) { } catch (Exception $e) { sendTelegram("Cron Reminder failed to send 7-day reminder to " . $ds['username'] . " Error: " . $e->getMessage()); } - } else if ($ds['expiration'] == $day3 && $config['notification_reminder_3day'] !== 'no') { + } else if ($ds['expiration'] == $day3 && $config['notification_reminder_3days'] !== 'no') { try { echo Message::sendPackageNotification( $c, diff --git a/system/devices/MikrotikPppoe.php b/system/devices/MikrotikPppoe.php index b3e5c9ec..9e10cfaa 100644 --- a/system/devices/MikrotikPppoe.php +++ b/system/devices/MikrotikPppoe.php @@ -244,7 +244,7 @@ class MikrotikPppoe function add_pool($pool){ global $_app_stage; - if ($_app_stage == 'demo') { + if ($_app_stage == 'Demo') { return null; } $mikrotik = $this->info($pool['routers']); @@ -259,7 +259,7 @@ class MikrotikPppoe function update_pool($old_pool, $new_pool){ global $_app_stage; - if ($_app_stage == 'demo') { + if ($_app_stage == 'Demo') { return null; } $mikrotik = $this->info($new_pool['routers']); @@ -284,7 +284,7 @@ class MikrotikPppoe function remove_pool($pool){ global $_app_stage; - if ($_app_stage == 'demo') { + if ($_app_stage == 'Demo') { return null; } $mikrotik = $this->info($pool['routers']); @@ -329,7 +329,7 @@ class MikrotikPppoe function getClient($ip, $user, $pass) { global $_app_stage; - if ($_app_stage == 'demo') { + if ($_app_stage == 'Demo') { return null; } $iport = explode(":", $ip); @@ -339,7 +339,7 @@ class MikrotikPppoe function removePpoeUser($client, $username) { global $_app_stage; - if ($_app_stage == 'demo') { + if ($_app_stage == 'Demo') { return null; } $printRequest = new RouterOS\Request('/ppp/secret/print'); @@ -376,7 +376,7 @@ class MikrotikPppoe function removePpoeActive($client, $username) { global $_app_stage; - if ($_app_stage == 'demo') { + if ($_app_stage == 'Demo') { return null; } $onlineRequest = new RouterOS\Request('/ppp/active/print'); @@ -392,7 +392,7 @@ class MikrotikPppoe function getIpHotspotUser($client, $username) { global $_app_stage; - if ($_app_stage == 'demo') { + if ($_app_stage == 'Demo') { return null; } $printRequest = new RouterOS\Request( @@ -405,7 +405,7 @@ class MikrotikPppoe function addIpToAddressList($client, $ip, $listName, $comment = '') { global $_app_stage; - if ($_app_stage == 'demo') { + if ($_app_stage == 'Demo') { return null; } $addRequest = new RouterOS\Request('/ip/firewall/address-list/add'); @@ -420,7 +420,7 @@ class MikrotikPppoe function removeIpFromAddressList($client, $ip) { global $_app_stage; - if ($_app_stage == 'demo') { + if ($_app_stage == 'Demo') { return null; } $printRequest = new RouterOS\Request( diff --git a/system/lan/english.json b/system/lan/english.json index f81d2f92..1f97dbcb 100644 --- a/system/lan/english.json +++ b/system/lan/english.json @@ -381,5 +381,166 @@ "_Clear_old_logs_": " Clear old logs?", "Clean_up_Logs": "Clean up Logs", "ID": "ID", - "Date_Sent": "Date Sent" + "Date_Sent": "Date Sent", + "Notification_Message": "Notification Message", + "Share": "Share", + "Previous": "Previous", + "Email_not_sent__Mailer_Error__": "Email not sent, Mailer Error: ", + "Invoice_Lists": "Invoice Lists", + "Invoices": "Invoices", + "Invoice_No": "Invoice No", + "Customer_Name": "Customer Name", + "Amount": "Amount", + "Due_Date": "Due Date", + "Actions": "Actions", + "Pending": "Pending", + "Send": "Send", + "": "", + "Commission": "Commission", + "Last_30_Days_Overall_Usage": "Last 30 Days Overall Usage", + "Last_30_Days_Total_Usage": "Last 30 Days Total Usage", + "Top_10_Downloaders": "Top 10 Downloaders", + "Rank": "Rank", + "Downloaded": "Downloaded", + "Upload_Data": "Upload Data", + "Download_Data": "Download Data", + "Total_Data": "Total Data", + "Service": "Service", + "Mac_Address": "Mac Address", + "Location": "Location", + "Last_Updated": "Last Updated", + "View_Details": "View Details", + "Successful_Payments": "Successful Payments", + "Failed_Payments": "Failed Payments", + "Pending_Payments": "Pending Payments", + "Cancelled_Payments": "Cancelled Payments", + "Refresh_Dashboard": "Refresh Dashboard", + "Daily_Sales": "Daily Sales", + "Monthly_Sales": "Monthly Sales", + "Weekly_Sales": "Weekly Sales", + "Hotspot_Vouchers_Overview": "Hotspot Vouchers Overview", + "Transaction_Ref": "Transaction Ref", + "Router_Name": "Router Name", + "Voucher_Code": "Voucher Code", + "Transaction_Status": "Transaction Status", + "Payment_Date": "Payment Date", + "Plan_Expiry_Date": "Plan Expiry Date", + "Action": "Action", + "Block_Mac_Address": "Block Mac Address", + "Unblock_Mac_Address": "Unblock Mac Address", + "Captive_Portal_Dashboard": "Captive Portal Dashboard", + "Manage_Router_NAS": "Manage Router\/NAS", + "Captive_Portal_Routers": "Captive Portal Routers", + "Add_New_Router": "Add New Router", + "Router_Details": "Router Details", + "MAP": "MAP", + "LANDING_PAGE": "LANDING PAGE", + "FILE": "FILE", + "EDIT": "EDIT", + "Edit_Router": "Edit Router", + "Description": "Description", + "Enter_Description": "Enter Description", + "Coordinate": "Coordinate", + "DELETE": "DELETE", + "Back_To_Dashboard": "Back To Dashboard", + "Device_Type": "Device Type", + "Mikrotik_API": "Mikrotik API", + "Choose_Router": "Choose Router", + "Choose": "Choose", + "Select": "Select", + "Add_Router": "Add Router", + "Router_Location": "Router Location", + "Go_Back": "Go Back", + "Preview": "Preview", + "Login_html": "Login.html", + "Branding": "Branding", + "Sliders": "Sliders", + "Advertisements": "Advertisements", + "Announcements": "Announcements", + "Integrations": "Integrations", + "Basic_Settings": "Basic Settings", + "Page_Title": "Page Title", + "Displayed_in_browser_title_bar": "Displayed in browser title bar", + "Page_description_browser_title_bar": "Page description browser title bar", + "Hotspot_Name": "Hotspot Name", + "Hotspot_Name_will_be_display_on_Login_Page_Nav_Bar_if_Logo_is_not_available": "Hotspot Name will be display on Login Page Nav Bar if Logo is not available", + "Footer_Text": "Footer Text", + "Allow_Free_Trial": "Allow Free Trial", + "Choose_No_if_you_dont_want_to_allow_Free_Trial": "Choose No if you dont want to allow Free Trial", + "Make_sure_you_enable_free_trial_in_Mikrotik_Router": "Make sure you enable free trial in Mikrotik Router", + "free_trial_button_wont_display_on_captive_portal_preview__but_will_work_if_you_connect_from_a_hotspot": "free trial button wont display on captive portal preview, but will work if you connect from a hotspot", + "Allow_Member_Login": "Allow Member Login", + "Choose_No_If_you_want_to_disable_Member_Login": "Choose No If you want to disable Member Login", + "Allow_Randomized_MAC": "Allow Randomized MAC", + "Choose_Yes_If_you_want_to_allow_Randomized_or_Private_MAC": "Choose Yes If you want to allow Randomized or Private MAC", + "Assign_Plan": "Assign Plan", + "Service_Plan": "Service Plan", + "Change": "Change", + "Select_Plans": "Select Plans", + "Choose_plan_that_will_that_your_users_will_be_assigned": "Choose plan that will that your users will be assigned", + "Restore_Default_Settings": "Restore Default Settings", + "Restore_Default": "Restore Default", + "Branding_Image": "Branding Image", + "Logo": "Logo", + "Backgrounds": "Backgrounds", + "Background_Type": "Background Type", + "Static_Color": "Static Color", + "Gradient_Color": "Gradient Color", + "Image": "Image", + "Static_Background_Color": "Static Background Color", + "Gradient_Background_Colors": "Gradient Background Colors", + "Gradient_Direction": "Gradient Direction", + "Left_to_Right": "Left to Right", + "Top_to_Bottom": "Top to Bottom", + "Right_to_Left": "Right to Left", + "Bottom_to_Top": "Bottom to Top", + "Choose_Image": "Choose Image", + "Buttons": "Buttons", + "Slider_Contact_Us_Button_BG_Color": "Slider Contact Us Button BG Color", + "Slider_Contact_Us_Button_Text_Color": "Slider Contact Us Button Text Color", + "Slider_Navigation_Button_BG_Color": "Slider Navigation Button BG Color", + "Slider_Navigation_Button_Icon_Color": "Slider Navigation Button Icon Color", + "Trial_Button_BG_Color": "Trial Button BG Color", + "Trial_Button_Text_Color": "Trial Button Text Color", + "Footer_Settings": "Footer Settings", + "Footer_Background_Color": "Footer Background Color", + "Misc": "Misc", + "Title": "Title", + "Link": "Link", + "Button": "Button", + "Add_New_Slider": "Add New Slider", + "Welcome_to_the_Captive_Portal": "Welcome to the Captive Portal", + "Button_Link": "Button Link", + "Button_Text": "Button Text", + "Click_Here": "Click Here", + "Target_Link": "Target Link", + "Create_New_Advert": "Create New Advert", + "Add_New_Advertisement": "Add New Advertisement", + "Content": "Content", + "Enter_Interstitial_Content": "Enter Interstitial Content", + "Upload_File": "Upload File", + "File_size_limit__5MB_for_images__100MB_for_videos": "File size limit: 5MB for images, 100MB for videos", + "Target_URL": "Target URL", + "Create_Advert": "Create Advert", + "You_cannot_perform_this_action_in_Demo_mode": "You cannot perform this action in Demo mode", + "Income_Today": "Income Today", + "Income_This_Month": "Income This Month", + "Customers": "Customers", + "Monthly_Registered_Customers": "Monthly Registered Customers", + "Cron_has_not_run_for_over_1_hour__Please_check_your_setup_": "Cron has not run for over 1 hour. Please check your setup.", + "Total_Monthly_Sales": "Total Monthly Sales", + "Customers_Expired__Today": "Customers Expired, Today", + "Phone": "Phone", + "Created___Expired": "Created \/ Expired", + "Internet_Package": "Internet Package", + "year": "year", + "month": "month", + "week": "week", + "day": "day", + "hour": "hour", + "minute": "minute", + "second": "second", + "ago": "ago", + "All_Users_Insights": "All Users Insights", + "Activity_Log": "Activity Log" } \ No newline at end of file diff --git a/system/updates.json b/system/updates.json index c0d490d9..8b17a0ed 100644 --- a/system/updates.json +++ b/system/updates.json @@ -1,210 +1,211 @@ { - "2023.8.9": [ - "ALTER TABLE `tbl_customers` ADD `balance` decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT 'For Money Deposit' AFTER `email`;", - "CREATE TABLE `tbl_customers_meta` (`id` int(11) NOT NULL, `customer_id` int(11) NOT NULL,`meta_key` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `meta_value` longtext COLLATE utf8mb4_general_ci) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;", - "ALTER TABLE `tbl_customers_meta` ADD PRIMARY KEY (`id`);", - "ALTER TABLE `tbl_customers_meta` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;" - ], - "2023.8.14": [ - "ALTER TABLE `tbl_customers` ADD `pppoe_password` varchar(45) NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;", - "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", - "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", - "ALTER TABLE `tbl_customers` ADD `auto_renewal` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Auto renewall using balance' AFTER `balance`;" - ], - "2023.8.23": [ - "ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';" - ], - "2023.8.28": [ - "ALTER TABLE `tbl_user_recharges` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;", - "ALTER TABLE `tbl_transactions` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;" - ], - "2023.9.5": [ - "DROP TABLE `tbl_language`;", - "ALTER TABLE `tbl_plans` ADD `pool_expired` varchar(40) NOT NULL DEFAULT '' AFTER `pool`;" - ], - "2023.9.27": [ - "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", - "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;" - ], - "2023.9.28": [ - "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", - "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;" - ], - "2023.10.1": [ - "ALTER TABLE `tbl_plans` ADD `is_radius` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '1 is radius' AFTER `routers`; " - ], - "2023.10.24": [ - "ALTER TABLE `nas` ADD `routers` VARCHAR(32) NOT NULL DEFAULT '' AFTER `description`;" - ], - "2023.12.15": [ - "ALTER TABLE `tbl_customers` ADD `service_type` ENUM('Hotspot','PPPoE','Others') DEFAULT 'Others' COMMENT 'For selecting user type' AFTER `balance`;" - ], - "2024.1.11": [ - "ALTER TABLE `tbl_plans` ADD `allow_purchase` ENUM('yes','no') DEFAULT 'yes' COMMENT 'allow to show package in buy package page' AFTER `enabled`;" - ], - "2024.2.7": [ - "ALTER TABLE `tbl_voucher` ADD `generated_by` INT NOT NULL DEFAULT '0' COMMENT 'id admin' AFTER `status`;", - "ALTER TABLE `tbl_users` ADD `root` INT NOT NULL DEFAULT '0' COMMENT 'for sub account' AFTER `id`;" - ], - "2024.2.12": [ - "ALTER TABLE `tbl_users` CHANGE `user_type` `user_type` ENUM('SuperAdmin','Admin','Report','Agent','Sales') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;" - ], - "2024.2.15": [ - "ALTER TABLE `tbl_users` CHANGE `password` `password` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", - "ALTER TABLE `tbl_users` ADD `phone` VARCHAR(32) NOT NULL DEFAULT '' AFTER `password`, ADD `email` VARCHAR(128) NOT NULL DEFAULT '' AFTER `phone`, ADD `city` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kota' AFTER `email`, ADD `subdistrict` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kecamatan' AFTER `city`, ADD `ward` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kelurahan' AFTER `subdistrict`;" - ], - "2024.2.16": [ - "ALTER TABLE `tbl_customers` ADD `created_by` INT NOT NULL DEFAULT '0' AFTER `auto_renewal`;" - ], - "2024.2.19": [ - "CREATE TABLE `tbl_customers_fields` (`id` INT PRIMARY KEY AUTO_INCREMENT, `customer_id` INT NOT NULL, `field_name` VARCHAR(255) NOT NULL, `field_value` VARCHAR(255) NOT NULL, FOREIGN KEY (customer_id) REFERENCES tbl_customers(id));" - ], - "2024.2.20": [ - "ALTER TABLE `tbl_plans` ADD `list_expired` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'address list' AFTER `pool_expired`;", - "ALTER TABLE `tbl_bandwidth` ADD `burst` VARCHAR(128) NOT NULL DEFAULT '' AFTER `rate_up_unit`;" - ], - "2024.2.20.1": [ - "DROP TABLE IF EXISTS `tbl_customers_meta`;" - ], - "2024.2.23": [ - "ALTER TABLE `tbl_transactions` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;", - "ALTER TABLE `tbl_user_recharges` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;" - ], - "2024.3.3": [ - "ALTER TABLE `tbl_plans` CHANGE `validity_unit` `validity_unit` ENUM('Mins','Hrs','Days','Months','Period') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;" - ], - "2024.3.12": [ - "ALTER TABLE `tbl_plans` CHANGE `allow_purchase` `prepaid` ENUM('yes','no') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'yes' COMMENT 'is prepaid';" - ], - "2024.3.14": [ - "ALTER TABLE `tbl_transactions` ADD `note` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'for note' AFTER `type`;" - ], - "2024.3.19": [ - "ALTER TABLE `tbl_customers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates' AFTER `email`;" - ], - "2024.3.19.1": [ - "ALTER TABLE `tbl_customers` ADD `account_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' AFTER `coordinates`;" - ], - "2024.3.19.2": [ - "ALTER TABLE `tbl_plans` ADD `plan_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' ;" - ], - "2023.3.20": [ - "ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';" - ], - "2024.4.5": [ - "ALTER TABLE `tbl_payment_gateway` ADD `trx_invoice` VARCHAR(25) NOT NULL DEFAULT '' COMMENT 'from tbl_transactions' AFTER `paid_date`;" - ], - "2024.5.17": [ - "ALTER TABLE `tbl_customers` ADD `status` ENUM('Active','Banned','Disabled') NOT NULL DEFAULT 'Active' AFTER `auto_renewal`;" - ], - "2024.5.18": [ - "ALTER TABLE `tbl_customers` CHANGE `status` `status` ENUM('Active','Banned','Disabled','Inactive','Limited','Suspended') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'Active';" - ], - "2024.5.20": [ - "ALTER TABLE `tbl_customers` ADD `city` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `address`, ADD `district` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `city`, ADD `state` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `district`, ADD `zip` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `state`;" - ], - "2024.6.5": [ - "ALTER TABLE `tbl_plans` ADD `price_old` VARCHAR(40) NOT NULL DEFAULT '' AFTER `price`;", - "ALTER TABLE `tbl_plans` ADD `device` VARCHAR(32) NOT NULL DEFAULT '' AFTER `plan_type`;" - ], - "2024.6.10": [ - "ALTER TABLE `tbl_pool` ADD `local_ip` VARCHAR(40) NOT NULL DEFAULT '' AFTER `pool_name`;" - ], - "2024.6.11": [ - "ALTER TABLE `tbl_plans` ADD `plan_expired` INT NOT NULL DEFAULT '0' AFTER `pool`;", - "ALTER TABLE `tbl_plans` DROP `pool_expired`, DROP `list_expired`;" - ], - "2024.6.19": [ - "ALTER TABLE `tbl_plans` ADD `expired_date` TINYINT(1) NOT NULL DEFAULT '20' AFTER `plan_expired`;" - ], - "2024.6.21": [ - "ALTER TABLE `tbl_plans` ADD `on_login` TEXT NULL DEFAULT NULL AFTER `device`;", - "ALTER TABLE `tbl_plans` ADD `on_logout` TEXT NULL DEFAULT NULL AFTER `on_login`;" - ], - "2024.7.6": [ - "CREATE TABLE IF NOT EXISTS `rad_acct` ( `id` bigint NOT NULL, `acctsessionid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `realm` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasid` varchar(32) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasportid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `nasporttype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `framedipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',`acctstatustype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `macaddr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `dateAdded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;", - "ALTER TABLE `rad_acct` ADD PRIMARY KEY (`id`), ADD KEY `username` (`username`), ADD KEY `framedipaddress` (`framedipaddress`), ADD KEY `acctsessionid` (`acctsessionid`), ADD KEY `nasipaddress` (`nasipaddress`);", - "ALTER TABLE `rad_acct` MODIFY `id` bigint NOT NULL AUTO_INCREMENT;" - ], - "2024.7.24": [ - "ALTER TABLE `tbl_voucher` ADD `used_date` DATETIME NULL DEFAULT NULL AFTER `status`;", - "UPDATE `tbl_voucher` SET `used_date`=now() WHERE `status`=1;" - ], - "2024.8.1": [ - "ALTER TABLE `tbl_payment_gateway` CHANGE `gateway_trx_id` `gateway_trx_id` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';", - "ALTER TABLE `tbl_payment_gateway` CHANGE `pg_url_payment` `pg_url_payment` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';" - ], - "2024.8.2": [ - "CREATE TABLE IF NOT EXISTS `tbl_customers_inbox` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT, `customer_id` int NOT NULL, `date_created` datetime NOT NULL, `date_read` datetime DEFAULT NULL, `subject` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `body` TEXT NULL DEFAULT NULL, `from` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'System' COMMENT 'System or Admin or Else',`admin_id` int NOT NULL DEFAULT '0' COMMENT 'other than admin is 0', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;" - ], - "2024.8.5": [ - "ALTER TABLE `tbl_customers` ADD `pppoe_username` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;", - "ALTER TABLE `tbl_customers` ADD `pppoe_ip` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `pppoe_password`;" - ], - "2024.8.5.1": [ - "ALTER TABLE `tbl_routers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' AFTER `description`;", - "ALTER TABLE `tbl_routers` ADD `coverage` VARCHAR(8) NOT NULL DEFAULT '0' AFTER `coordinates`;" - ], - "2024.8.6": [ - "ALTER TABLE `rad_acct` ADD `acctinputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `framedipaddress`;", - "ALTER TABLE `rad_acct` ADD `acctoutputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `acctinputoctets`;" - ], - "2024.8.7": [ - "ALTER TABLE `tbl_customers` CHANGE `coordinates` `coordinates` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates';" - ], - "2024.8.28": [ - "ALTER TABLE `tbl_routers` ADD `status` ENUM('Online', 'Offline') DEFAULT 'Online' AFTER `coordinates`;", - "ALTER TABLE `tbl_routers` ADD `last_seen` DATETIME AFTER `status`;" - ], - "2024.9.13": [ - "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", - "ALTER TABLE `tbl_customers` CHANGE `service_type` `service_type` ENUM('Hotspot','PPPoE','VPN','Others') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'Others' COMMENT 'For selecting user type';", - "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", - "CREATE TABLE IF NOT EXISTS `tbl_port_pool` ( `id` int(10) NOT NULL AUTO_INCREMENT , `public_ip` varchar(40) NOT NULL, `port_name` varchar(40) NOT NULL, `range_port` varchar(40) NOT NULL, `routers` varchar(40) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;" - ], - "2024.10.10": [ - "ALTER TABLE `tbl_users` ADD `login_token` VARCHAR(40) AFTER `last_login`;" - ], - "2024.10.17": [ - "CREATE TABLE IF NOT EXISTS `tbl_meta` ( `id` int UNSIGNED NOT NULL AUTO_INCREMENT, `tbl` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Table name', `tbl_id` int NOT NULL COMMENT 'table value id', `name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL, `value` mediumtext COLLATE utf8mb4_general_ci, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='This Table to add additional data for any table';" - ], - "2024.10.30": [ - "ALTER TABLE `tbl_users` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/admin.default.png' AFTER `root`;", - "ALTER TABLE `tbl_users` ADD `data` TEXT NULL DEFAULT NULL COMMENT 'to put additional data' AFTER `status`;" - ], - "2024.10.31": [ - "ALTER TABLE `tbl_customers` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/user.default.jpg' AFTER `password`;" - ], - "2024.12.5.1": [ - "ALTER TABLE `tbl_transactions` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;", - "ALTER TABLE `tbl_payment_gateway` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;" - ], - "2024.12.16": [ - "CREATE TABLE `tbl_coupons` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `code` VARCHAR(50) NOT NULL UNIQUE, `type` ENUM('fixed', 'percent') NOT NULL, `value` DECIMAL(10,2) NOT NULL, `description` TEXT NOT NULL, `max_usage` INT NOT NULL DEFAULT 1,`usage_count` INT NOT NULL DEFAULT 0,`status` ENUM('active', 'inactive') NOT NULL, `min_order_amount` DECIMAL(10,2) NOT NULL, `max_discount_amount` DECIMAL(10,2) NOT NULL, `start_date` DATE NOT NULL,`end_date` DATE NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);" - ], - "2024.12.20": [ - "ALTER TABLE `tbl_voucher` ADD `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;" - ], - "2025.1.23": [ - "ALTER TABLE `rad_acct` ADD `acctsessiontime` BIGINT(12) NOT NULL DEFAULT '0' AFTER `framedipaddress`;" - ], - "2025.2.14": [ - "CREATE TABLE IF NOT EXISTS `tbl_widgets` ( `id` int NOT NULL AUTO_INCREMENT, `orders` int NOT NULL DEFAULT '99', `position` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1. top 2. left 3. right 4. bottom',`enabled` tinyint(1) NOT NULL DEFAULT '1', `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `widget` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `content` text COLLATE utf8mb4_general_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;" - ], - "2025.2.17" : [ - "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `enabled`, `title`, `widget`, `content`) VALUES (1, 1, 1, 1, 'Top Widget', 'top_widget', ''),(2, 2, 1, 1, 'Default Info', 'default_info_row', ''),(3, 1, 2, 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''),(4, 2, 2, 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''),(5, 3, 2, 1, 'Voucher Stocks', 'voucher_stocks', ''),(6, 4, 2, 1, 'Customer Expired', 'customer_expired', ''),(7, 1, 3, 1, 'Cron Monitor', 'cron_monitor', ''),(8, 2, 3, 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''),(9, 3, 3, 1, 'Info Payment Gateway', 'info_payment_gateway', ''),(10, 4, 3, 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(11, 5, 3, 1, 'Activity Log', 'activity_log', '');" - ], - "2025.2.19" : [ - "ALTER TABLE `tbl_widgets` ADD `user` ENUM('Admin','Agent','Sales','Customer') NOT NULL DEFAULT 'Admin' AFTER `position`;" - ], - "2025.2.21" : [ - "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (60, 1, 2, 'Customer', 1, 'Account Info', 'account_info', ''),(61, 3, 1, 'Customer', 1, 'Active Internet Plan', 'active_internet_plan', ''),(62, 4, 1, 'Customer', 1, 'Balance Transfer', 'balance_transfer', ''),(63, 1, 1, 'Customer', 1, 'Unpaid Order', 'unpaid_order', ''),(64, 2, 1, 'Customer', 1, 'Announcement', 'announcement', ''),(65, 5, 1, 'Customer', 1, 'Recharge A Friend', 'recharge_a_friend', ''),(66, 2, 2, 'Customer', 1, 'Voucher Activation', 'voucher_activation', '');" - ], - "2025.2.25" : [ - "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (30, 1, 1, 'Agent', 1, 'Top Widget', 'top_widget', ''), (31, 2, 1, 'Agent', 1, 'Default Info', 'default_info_row', ''), (32, 1, 2, 'Agent', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (33, 2, 2, 'Agent', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (34, 3, 2, 'Agent', 1, 'Voucher Stocks', 'voucher_stocks', ''), (35, 4, 2, 'Agent', 1, 'Customer Expired', 'customer_expired', ''), (36, 1, 3, 'Agent', 1, 'Cron Monitor', 'cron_monitor', ''), (37, 2, 3, 'Agent', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (38, 3, 3, 'Agent', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (39, 4, 3, 'Agent', 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(40, 5, 3, 'Agent', 1, 'Activity Log', 'activity_log', '');", - "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (41, 1, 1, 'Sales', 1, 'Top Widget', 'top_widget', ''), (42, 2, 1, 'Sales', 1, 'Default Info', 'default_info_row', ''), (43, 1, 2, 'Sales', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (44, 2, 2, 'Sales', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (45, 3, 2, 'Sales', 1, 'Voucher Stocks', 'voucher_stocks', ''), (46, 4, 2, 'Sales', 1, 'Customer Expired', 'customer_expired', ''), (47, 1, 3, 'Sales', 1, 'Cron Monitor', 'cron_monitor', ''), (48, 2, 3, 'Sales', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (49, 3, 3, 'Sales', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (50, 4, 3, 'Sales', 1, 'Graph Customers Insight', 'graph_customers_insight', ''), (51, 5, 3, 'Sales', 1, 'Activity Log', 'activity_log', '');" - ], - "2025.3.5" : [ - "CREATE TABLE IF NOT EXISTS `tbl_message_logs` ( `id` SERIAL PRIMARY KEY, `message_type` VARCHAR(50), `recipient` VARCHAR(255), `message_content` TEXT, `status` VARCHAR(50), `error_message` TEXT, `sent_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;" - ] -} \ No newline at end of file + "2023.8.9": [ + "ALTER TABLE `tbl_customers` ADD `balance` decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT 'For Money Deposit' AFTER `email`;", + "CREATE TABLE `tbl_customers_meta` (`id` int(11) NOT NULL, `customer_id` int(11) NOT NULL,`meta_key` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `meta_value` longtext COLLATE utf8mb4_general_ci) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;", + "ALTER TABLE `tbl_customers_meta` ADD PRIMARY KEY (`id`);", + "ALTER TABLE `tbl_customers_meta` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;" + ], + "2023.8.14": [ + "ALTER TABLE `tbl_customers` ADD `pppoe_password` varchar(45) NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;", + "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", + "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", + "ALTER TABLE `tbl_customers` ADD `auto_renewal` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Auto renewall using balance' AFTER `balance`;" + ], + "2023.8.23": [ + "ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';" + ], + "2023.8.28": [ + "ALTER TABLE `tbl_user_recharges` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;", + "ALTER TABLE `tbl_transactions` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;" + ], + "2023.9.5": [ + "DROP TABLE `tbl_language`;", + "ALTER TABLE `tbl_plans` ADD `pool_expired` varchar(40) NOT NULL DEFAULT '' AFTER `pool`;" + ], + "2023.9.27": [ + "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", + "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;" + ], + "2023.9.28": [ + "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", + "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;" + ], + "2023.10.1": [ + "ALTER TABLE `tbl_plans` ADD `is_radius` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '1 is radius' AFTER `routers`; " + ], + "2023.10.24": [ + "ALTER TABLE `nas` ADD `routers` VARCHAR(32) NOT NULL DEFAULT '' AFTER `description`;" + ], + "2023.12.15": [ + "ALTER TABLE `tbl_customers` ADD `service_type` ENUM('Hotspot','PPPoE','Others') DEFAULT 'Others' COMMENT 'For selecting user type' AFTER `balance`;" + ], + "2024.1.11": [ + "ALTER TABLE `tbl_plans` ADD `allow_purchase` ENUM('yes','no') DEFAULT 'yes' COMMENT 'allow to show package in buy package page' AFTER `enabled`;" + ], + "2024.2.7": [ + "ALTER TABLE `tbl_voucher` ADD `generated_by` INT NOT NULL DEFAULT '0' COMMENT 'id admin' AFTER `status`;", + "ALTER TABLE `tbl_users` ADD `root` INT NOT NULL DEFAULT '0' COMMENT 'for sub account' AFTER `id`;" + ], + "2024.2.12": [ + "ALTER TABLE `tbl_users` CHANGE `user_type` `user_type` ENUM('SuperAdmin','Admin','Report','Agent','Sales') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;" + ], + "2024.2.15": [ + "ALTER TABLE `tbl_users` CHANGE `password` `password` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", + "ALTER TABLE `tbl_users` ADD `phone` VARCHAR(32) NOT NULL DEFAULT '' AFTER `password`, ADD `email` VARCHAR(128) NOT NULL DEFAULT '' AFTER `phone`, ADD `city` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kota' AFTER `email`, ADD `subdistrict` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kecamatan' AFTER `city`, ADD `ward` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kelurahan' AFTER `subdistrict`;" + ], + "2024.2.16": [ + "ALTER TABLE `tbl_customers` ADD `created_by` INT NOT NULL DEFAULT '0' AFTER `auto_renewal`;" + ], + "2024.2.19": [ + "CREATE TABLE `tbl_customers_fields` (`id` INT PRIMARY KEY AUTO_INCREMENT, `customer_id` INT NOT NULL, `field_name` VARCHAR(255) NOT NULL, `field_value` VARCHAR(255) NOT NULL, FOREIGN KEY (customer_id) REFERENCES tbl_customers(id));" + ], + "2024.2.20": [ + "ALTER TABLE `tbl_plans` ADD `list_expired` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'address list' AFTER `pool_expired`;", + "ALTER TABLE `tbl_bandwidth` ADD `burst` VARCHAR(128) NOT NULL DEFAULT '' AFTER `rate_up_unit`;" + ], + "2024.2.20.1": ["DROP TABLE IF EXISTS `tbl_customers_meta`;"], + "2024.2.23": [ + "ALTER TABLE `tbl_transactions` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;", + "ALTER TABLE `tbl_user_recharges` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;" + ], + "2024.3.3": [ + "ALTER TABLE `tbl_plans` CHANGE `validity_unit` `validity_unit` ENUM('Mins','Hrs','Days','Months','Period') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;" + ], + "2024.3.12": [ + "ALTER TABLE `tbl_plans` CHANGE `allow_purchase` `prepaid` ENUM('yes','no') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'yes' COMMENT 'is prepaid';" + ], + "2024.3.14": [ + "ALTER TABLE `tbl_transactions` ADD `note` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'for note' AFTER `type`;" + ], + "2024.3.19": [ + "ALTER TABLE `tbl_customers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates' AFTER `email`;" + ], + "2024.3.19.1": [ + "ALTER TABLE `tbl_customers` ADD `account_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' AFTER `coordinates`;" + ], + "2024.3.19.2": [ + "ALTER TABLE `tbl_plans` ADD `plan_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' ;" + ], + "2023.3.20": [ + "ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';" + ], + "2024.4.5": [ + "ALTER TABLE `tbl_payment_gateway` ADD `trx_invoice` VARCHAR(25) NOT NULL DEFAULT '' COMMENT 'from tbl_transactions' AFTER `paid_date`;" + ], + "2024.5.17": [ + "ALTER TABLE `tbl_customers` ADD `status` ENUM('Active','Banned','Disabled') NOT NULL DEFAULT 'Active' AFTER `auto_renewal`;" + ], + "2024.5.18": [ + "ALTER TABLE `tbl_customers` CHANGE `status` `status` ENUM('Active','Banned','Disabled','Inactive','Limited','Suspended') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'Active';" + ], + "2024.5.20": [ + "ALTER TABLE `tbl_customers` ADD `city` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `address`, ADD `district` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `city`, ADD `state` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `district`, ADD `zip` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `state`;" + ], + "2024.6.5": [ + "ALTER TABLE `tbl_plans` ADD `price_old` VARCHAR(40) NOT NULL DEFAULT '' AFTER `price`;", + "ALTER TABLE `tbl_plans` ADD `device` VARCHAR(32) NOT NULL DEFAULT '' AFTER `plan_type`;" + ], + "2024.6.10": [ + "ALTER TABLE `tbl_pool` ADD `local_ip` VARCHAR(40) NOT NULL DEFAULT '' AFTER `pool_name`;" + ], + "2024.6.11": [ + "ALTER TABLE `tbl_plans` ADD `plan_expired` INT NOT NULL DEFAULT '0' AFTER `pool`;", + "ALTER TABLE `tbl_plans` DROP `pool_expired`, DROP `list_expired`;" + ], + "2024.6.19": [ + "ALTER TABLE `tbl_plans` ADD `expired_date` TINYINT(1) NOT NULL DEFAULT '20' AFTER `plan_expired`;" + ], + "2024.6.21": [ + "ALTER TABLE `tbl_plans` ADD `on_login` TEXT NULL DEFAULT NULL AFTER `device`;", + "ALTER TABLE `tbl_plans` ADD `on_logout` TEXT NULL DEFAULT NULL AFTER `on_login`;" + ], + "2024.7.6": [ + "CREATE TABLE IF NOT EXISTS `rad_acct` ( `id` bigint NOT NULL, `acctsessionid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `realm` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasid` varchar(32) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasportid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `nasporttype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `framedipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',`acctstatustype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `macaddr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `dateAdded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;", + "ALTER TABLE `rad_acct` ADD PRIMARY KEY (`id`), ADD KEY `username` (`username`), ADD KEY `framedipaddress` (`framedipaddress`), ADD KEY `acctsessionid` (`acctsessionid`), ADD KEY `nasipaddress` (`nasipaddress`);", + "ALTER TABLE `rad_acct` MODIFY `id` bigint NOT NULL AUTO_INCREMENT;" + ], + "2024.7.24": [ + "ALTER TABLE `tbl_voucher` ADD `used_date` DATETIME NULL DEFAULT NULL AFTER `status`;", + "UPDATE `tbl_voucher` SET `used_date`=now() WHERE `status`=1;" + ], + "2024.8.1": [ + "ALTER TABLE `tbl_payment_gateway` CHANGE `gateway_trx_id` `gateway_trx_id` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';", + "ALTER TABLE `tbl_payment_gateway` CHANGE `pg_url_payment` `pg_url_payment` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';" + ], + "2024.8.2": [ + "CREATE TABLE IF NOT EXISTS `tbl_customers_inbox` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT, `customer_id` int NOT NULL, `date_created` datetime NOT NULL, `date_read` datetime DEFAULT NULL, `subject` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `body` TEXT NULL DEFAULT NULL, `from` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'System' COMMENT 'System or Admin or Else',`admin_id` int NOT NULL DEFAULT '0' COMMENT 'other than admin is 0', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;" + ], + "2024.8.5": [ + "ALTER TABLE `tbl_customers` ADD `pppoe_username` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;", + "ALTER TABLE `tbl_customers` ADD `pppoe_ip` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `pppoe_password`;" + ], + "2024.8.5.1": [ + "ALTER TABLE `tbl_routers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' AFTER `description`;", + "ALTER TABLE `tbl_routers` ADD `coverage` VARCHAR(8) NOT NULL DEFAULT '0' AFTER `coordinates`;" + ], + "2024.8.6": [ + "ALTER TABLE `rad_acct` ADD `acctinputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `framedipaddress`;", + "ALTER TABLE `rad_acct` ADD `acctoutputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `acctinputoctets`;" + ], + "2024.8.7": [ + "ALTER TABLE `tbl_customers` CHANGE `coordinates` `coordinates` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates';" + ], + "2024.8.28": [ + "ALTER TABLE `tbl_routers` ADD `status` ENUM('Online', 'Offline') DEFAULT 'Online' AFTER `coordinates`;", + "ALTER TABLE `tbl_routers` ADD `last_seen` DATETIME AFTER `status`;" + ], + "2024.9.13": [ + "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", + "ALTER TABLE `tbl_customers` CHANGE `service_type` `service_type` ENUM('Hotspot','PPPoE','VPN','Others') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'Others' COMMENT 'For selecting user type';", + "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", + "CREATE TABLE IF NOT EXISTS `tbl_port_pool` ( `id` int(10) NOT NULL AUTO_INCREMENT , `public_ip` varchar(40) NOT NULL, `port_name` varchar(40) NOT NULL, `range_port` varchar(40) NOT NULL, `routers` varchar(40) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;" + ], + "2024.10.10": [ + "ALTER TABLE `tbl_users` ADD `login_token` VARCHAR(40) AFTER `last_login`;" + ], + "2024.10.17": [ + "CREATE TABLE IF NOT EXISTS `tbl_meta` ( `id` int UNSIGNED NOT NULL AUTO_INCREMENT, `tbl` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Table name', `tbl_id` int NOT NULL COMMENT 'table value id', `name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL, `value` mediumtext COLLATE utf8mb4_general_ci, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='This Table to add additional data for any table';" + ], + "2024.10.30": [ + "ALTER TABLE `tbl_users` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/admin.default.png' AFTER `root`;", + "ALTER TABLE `tbl_users` ADD `data` TEXT NULL DEFAULT NULL COMMENT 'to put additional data' AFTER `status`;" + ], + "2024.10.31": [ + "ALTER TABLE `tbl_customers` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/user.default.jpg' AFTER `password`;" + ], + "2024.12.5.1": [ + "ALTER TABLE `tbl_transactions` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;", + "ALTER TABLE `tbl_payment_gateway` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;" + ], + "2024.12.16": [ + "CREATE TABLE `tbl_coupons` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `code` VARCHAR(50) NOT NULL UNIQUE, `type` ENUM('fixed', 'percent') NOT NULL, `value` DECIMAL(10,2) NOT NULL, `description` TEXT NOT NULL, `max_usage` INT NOT NULL DEFAULT 1,`usage_count` INT NOT NULL DEFAULT 0,`status` ENUM('active', 'inactive') NOT NULL, `min_order_amount` DECIMAL(10,2) NOT NULL, `max_discount_amount` DECIMAL(10,2) NOT NULL, `start_date` DATE NOT NULL,`end_date` DATE NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);" + ], + "2024.12.20": [ + "ALTER TABLE `tbl_voucher` ADD `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;" + ], + "2025.1.23": [ + "ALTER TABLE `rad_acct` ADD `acctsessiontime` BIGINT(12) NOT NULL DEFAULT '0' AFTER `framedipaddress`;" + ], + "2025.2.14": [ + "CREATE TABLE IF NOT EXISTS `tbl_widgets` ( `id` int NOT NULL AUTO_INCREMENT, `orders` int NOT NULL DEFAULT '99', `position` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1. top 2. left 3. right 4. bottom',`enabled` tinyint(1) NOT NULL DEFAULT '1', `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `widget` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `content` text COLLATE utf8mb4_general_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;" + ], + "2025.2.17": [ + "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `enabled`, `title`, `widget`, `content`) VALUES (1, 1, 1, 1, 'Top Widget', 'top_widget', ''),(2, 2, 1, 1, 'Default Info', 'default_info_row', ''),(3, 1, 2, 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''),(4, 2, 2, 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''),(5, 3, 2, 1, 'Voucher Stocks', 'voucher_stocks', ''),(6, 4, 2, 1, 'Customer Expired', 'customer_expired', ''),(7, 1, 3, 1, 'Cron Monitor', 'cron_monitor', ''),(8, 2, 3, 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''),(9, 3, 3, 1, 'Info Payment Gateway', 'info_payment_gateway', ''),(10, 4, 3, 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(11, 5, 3, 1, 'Activity Log', 'activity_log', '');" + ], + "2025.2.19": [ + "ALTER TABLE `tbl_widgets` ADD `user` ENUM('Admin','Agent','Sales','Customer') NOT NULL DEFAULT 'Admin' AFTER `position`;" + ], + "2025.2.21": [ + "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (60, 1, 2, 'Customer', 1, 'Account Info', 'account_info', ''),(61, 3, 1, 'Customer', 1, 'Active Internet Plan', 'active_internet_plan', ''),(62, 4, 1, 'Customer', 1, 'Balance Transfer', 'balance_transfer', ''),(63, 1, 1, 'Customer', 1, 'Unpaid Order', 'unpaid_order', ''),(64, 2, 1, 'Customer', 1, 'Announcement', 'announcement', ''),(65, 5, 1, 'Customer', 1, 'Recharge A Friend', 'recharge_a_friend', ''),(66, 2, 2, 'Customer', 1, 'Voucher Activation', 'voucher_activation', '');" + ], + "2025.2.25": [ + "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (30, 1, 1, 'Agent', 1, 'Top Widget', 'top_widget', ''), (31, 2, 1, 'Agent', 1, 'Default Info', 'default_info_row', ''), (32, 1, 2, 'Agent', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (33, 2, 2, 'Agent', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (34, 3, 2, 'Agent', 1, 'Voucher Stocks', 'voucher_stocks', ''), (35, 4, 2, 'Agent', 1, 'Customer Expired', 'customer_expired', ''), (36, 1, 3, 'Agent', 1, 'Cron Monitor', 'cron_monitor', ''), (37, 2, 3, 'Agent', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (38, 3, 3, 'Agent', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (39, 4, 3, 'Agent', 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(40, 5, 3, 'Agent', 1, 'Activity Log', 'activity_log', '');", + "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (41, 1, 1, 'Sales', 1, 'Top Widget', 'top_widget', ''), (42, 2, 1, 'Sales', 1, 'Default Info', 'default_info_row', ''), (43, 1, 2, 'Sales', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (44, 2, 2, 'Sales', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (45, 3, 2, 'Sales', 1, 'Voucher Stocks', 'voucher_stocks', ''), (46, 4, 2, 'Sales', 1, 'Customer Expired', 'customer_expired', ''), (47, 1, 3, 'Sales', 1, 'Cron Monitor', 'cron_monitor', ''), (48, 2, 3, 'Sales', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (49, 3, 3, 'Sales', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (50, 4, 3, 'Sales', 1, 'Graph Customers Insight', 'graph_customers_insight', ''), (51, 5, 3, 'Sales', 1, 'Activity Log', 'activity_log', '');" + ], + "2025.3.5": [ + "CREATE TABLE IF NOT EXISTS `tbl_message_logs` ( `id` SERIAL PRIMARY KEY, `message_type` VARCHAR(50), `recipient` VARCHAR(255), `message_content` TEXT, `status` VARCHAR(50), `error_message` TEXT, `sent_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;" + ], + "2025.3.10": [ + "CREATE TABLE IF NOT EXISTS `tbl_invoices` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `number` VARCHAR(50) NOT NULL, `customer_id` INT NOT NULL, `fullname` VARCHAR(100) NOT NULL, `email` VARCHAR(100) NOT NULL, `address` TEXT, `status` ENUM('Unpaid', 'Paid', 'Cancelled') NOT NULL DEFAULT 'Unpaid', `due_date` DATETIME NOT NULL, `filename` VARCHAR(255), `amount` DECIMAL(10, 2) NOT NULL, `data` JSON NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP);" + ] +} diff --git a/system/uploads/invoices/index.html b/system/uploads/invoices/index.html new file mode 100644 index 00000000..e69de29b diff --git a/ui/ui/admin/customers/list.tpl b/ui/ui/admin/customers/list.tpl index 8906dd41..3d2ab3f0 100644 --- a/ui/ui/admin/customers/list.tpl +++ b/ui/ui/admin/customers/list.tpl @@ -18,9 +18,9 @@ {if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
CSV + href="{Text::url('customers/csv&token=', $csrf_token)}" onclick="return ask(this, '{Lang::T(" + This will export to CSV")}?')"> CSV
{/if} {Lang::T('Manage Contact')} @@ -205,14 +205,15 @@ @@ -260,6 +261,8 @@ $('#sendMessageButton').on('click', function () { const message = $('#messageContent').val().trim(); const messageType = $('#messageType').val(); + const subject = $('#subject-content').val().trim(); + if (!message) { Swal.fire({ @@ -271,6 +274,16 @@ return; } + if (messageType == 'all' || messageType == 'inbox' || messageType == 'email' && !subject) { + Swal.fire({ + title: 'Error!', + text: "{Lang::T('Please enter a subject for the message.')}", + icon: 'error', + confirmButtonText: 'OK' + }); + return; + } + // Disable the button and show loading text $(this).prop('disabled', true).text('{Lang::T('Sending...')}'); @@ -332,4 +345,31 @@ }); }); + {include file = "sections/footer.tpl" } \ No newline at end of file diff --git a/ui/ui/admin/header.tpl b/ui/ui/admin/header.tpl index 9ab008c6..18ad53de 100644 --- a/ui/ui/admin/header.tpl +++ b/ui/ui/admin/header.tpl @@ -14,6 +14,7 @@ + @@ -29,7 +30,7 @@ {if isset($xheader)} - {$xheader} + {$xheader} {/if} @@ -77,8 +78,8 @@ @@ -395,11 +403,11 @@ {if $_c['maintenance_mode'] == 1} -
-

{Lang::T('The website is currently in maintenance mode, this means that some or all functionality may be +

+

{Lang::T('The website is currently in maintenance mode, this means that some or all functionality may be unavailable to regular users during this time.')}   {Lang::T('Turn Off')}

-
+ href="{Text::url('settings/maintenance')}">{Lang::T('Turn Off')}

+
{/if}
@@ -411,19 +419,19 @@
{if isset($notify)} - -{/if} \ No newline at end of file + + {/if} \ No newline at end of file diff --git a/ui/ui/admin/invoices/list.tpl b/ui/ui/admin/invoices/list.tpl new file mode 100644 index 00000000..a2531310 --- /dev/null +++ b/ui/ui/admin/invoices/list.tpl @@ -0,0 +1,57 @@ +{include file="sections/header.tpl"} + + +
+
{Lang::T('Invoices')}
+
+
' . Lang::T('Username') . '' . Lang::T('Fullname') . ' ' . Lang::T('Plan Name') . ' ' . Lang::T('Type') . ' ' . Lang::T('Plan Price') . '$username$plan_name$fullname$plan_name $type $price $recharged_on
+ + + + + + + + + + + + + + + {foreach $invoices as $invoice} + + + + + + + + + + + + {/foreach} + +
{Lang::T('Invoice No')}{Lang::T('Customer Name')}{Lang::T('Email')}{Lang::T('Address')}{Lang::T('Amount')}{Lang::T('Status')}{Lang::T('Created Date')}{Lang::T('Due Date')}{Lang::T('Actions')}
{$invoice->number}{$invoice->fullname}{$invoice->email}{$invoice->address}{$invoice->amount} + {if $invoice->status == 'paid'} + {Lang::T('Paid')} + {elseif $invoice->status == 'unpaid'} + {Lang::T('Unpaid')} + {else} + {Lang::T('Pending')} + {/if} + {$invoice->created_at}{$invoice->due_date} + {Lang::T('View')} + +
+ + + + + + +{include file="sections/footer.tpl"} \ No newline at end of file diff --git a/ui/ui/admin/message/bulk.tpl b/ui/ui/admin/message/bulk.tpl index a8a57dba..7d7c677a 100644 --- a/ui/ui/admin/message/bulk.tpl +++ b/ui/ui/admin/message/bulk.tpl @@ -30,7 +30,8 @@ @@ -41,8 +42,10 @@ @@ -50,9 +53,13 @@
@@ -72,10 +79,21 @@ {Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')} +
+ +
+ +
+

+ {Lang::T('You can also use the below placeholders here too')}. +

+
- + {Lang::T('Testing [if checked no real message is sent]')}
@@ -93,7 +111,8 @@
- + {Lang::T('Cancel')}
@@ -112,7 +131,7 @@ {Lang::T('Customer')} - {Lang::T('Phone')} + {Lang::T('Channel')} {Lang::T('Status')} {Lang::T('Message')} {Lang::T('Router')} @@ -126,6 +145,34 @@ + {literal} {include file="sections/footer.tpl"} \ No newline at end of file diff --git a/ui/ui/admin/print/by-period.tpl b/ui/ui/admin/print/by-period.tpl index 78f20828..01f7821f 100644 --- a/ui/ui/admin/print/by-period.tpl +++ b/ui/ui/admin/print/by-period.tpl @@ -26,6 +26,7 @@
+ @@ -36,6 +37,7 @@ {foreach $d as $ds} + diff --git a/ui/ui/admin/reports/activation.tpl b/ui/ui/admin/reports/activation.tpl index 4ced97a9..1dc09a94 100644 --- a/ui/ui/admin/reports/activation.tpl +++ b/ui/ui/admin/reports/activation.tpl @@ -33,6 +33,7 @@ + @@ -48,6 +49,7 @@ style="cursor:pointer;">{$ds['invoice']} + diff --git a/ui/ui/admin/reports/list.tpl b/ui/ui/admin/reports/list.tpl index deb249b6..356ebec0 100644 --- a/ui/ui/admin/reports/list.tpl +++ b/ui/ui/admin/reports/list.tpl @@ -94,10 +94,11 @@ - + + @@ -111,6 +112,7 @@ {foreach $d as $ds} + @@ -122,9 +124,8 @@ {/foreach} - - +
{Lang::T('Username')}{Lang::T('Fullname')} {Lang::T('Plan Name')} {Lang::T('Type')} {Lang::T('Plan Price')}
{$ds['username']}{$ds['fullname']} {$ds['plan_name']} {$ds['type']} {Lang::moneyFormat($ds['price'])}
{Lang::T('Invoice')} {Lang::T('Username')}{Lang::T('Fullname')} {Lang::T('Plan Name')} {Lang::T('Plan Price')} {Lang::T('Type')} {$ds['username']}{$ds['fullname']} {$ds['plan_name']} {Lang::moneyFormat($ds['price'])} {$ds['type']}
{Lang::T('Username')}{Lang::T('Fullname')} {Lang::T('Type')} {Lang::T('Plan Name')} {Lang::T('Plan Price')}
{$ds['username']}{$ds['fullname']} {$ds['type']} {$ds['plan_name']} {Lang::moneyFormat($ds['price'])}
{Lang::T('Total')} {Lang::moneyFormat($dr)}
diff --git a/ui/ui/admin/reports/period-view.tpl b/ui/ui/admin/reports/period-view.tpl index d310248e..4283013c 100644 --- a/ui/ui/admin/reports/period-view.tpl +++ b/ui/ui/admin/reports/period-view.tpl @@ -37,6 +37,7 @@ {Lang::T('Username')} + {Lang::T('Fullname')} {Lang::T('Type')} {Lang::T('Plan Name')} {Lang::T('Plan Price')} @@ -50,6 +51,7 @@ {foreach $d as $ds} {$ds['username']} + {$ds['fullname']} {$ds['type']} {$ds['plan_name']} {Lang::moneyFormat($ds['price'])} diff --git a/ui/ui/admin/settings/app.tpl b/ui/ui/admin/settings/app.tpl index b5164781..ba359055 100644 --- a/ui/ui/admin/settings/app.tpl +++ b/ui/ui/admin/settings/app.tpl @@ -155,7 +155,7 @@

- {Lang::T('Customer Login Page Settings')} + {Lang::T('Customer Login Page')}

@@ -254,6 +254,37 @@ +
+ +
+
+
+ +
+ +
+

+ {Lang::T('Enable or disable coupons')} +

+
+ +
+
+
+
{if isset($_c['CompanyFooter'])} - + {else} - + {/if}
@@ -50,156 +50,175 @@ {if isset($xfooter)} - {$xfooter} +{$xfooter} {/if} {if $_c['tawkto'] != ''} - - - - {/if} + (function () { + var s1 = document.createElement("script"), + s0 = document.getElementsByTagName("script")[0]; + s1.async = true; + s1.src = 'https://embed.tawk.to/{$_c['tawkto']}'; + s1.charset = 'UTF-8'; + s1.setAttribute('crossorigin', '*'); + s0.parentNode.insertBefore(s1, s0); + })(); + + +{/if} - + }); + {literal} - + return null; + } + {/literal}