feat: Enhance software tool with country selection and tax calculation

- Added a helper function to generate country select options in software tool.
- Updated user info modal and payment modal to use country dropdowns instead of text inputs.
- Implemented tax calculation based on selected country in payment modal.
- Improved software options loading behavior in debug mode.
- Enhanced description formatting in payment modal.
- Added log modal for equipment updates with a link to view logs.
- Introduced a new countries settings file with tax rates for various countries.
- Minor adjustments to various PHP files for better handling of equipment and payment processes.
This commit is contained in:
“VeLiTi”
2026-01-16 16:01:31 +01:00
parent 7aebb762d3
commit 3db13b9ebf
27 changed files with 652 additions and 114 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.gitignore vendored
View File

@@ -26,3 +26,4 @@ api/v2/.DS_Store
api/.DS_Store api/.DS_Store
assets/.DS_Store assets/.DS_Store
assets/images/.DS_Store assets/images/.DS_Store
assets/database/ManualUpdates.sql

12
api.php
View File

@@ -1,6 +1,7 @@
<?php <?php
define('secure_34563$52', true); define('secure_34563$52', true);
//------------------------------------------ //------------------------------------------
// Get DATA from API // Get DATA from API
//------------------------------------------ //------------------------------------------
@@ -16,6 +17,17 @@ require_once './assets/functions.php';
include './settings/settings_redirector.php'; include './settings/settings_redirector.php';
include './settings/config_redirector.php'; include './settings/config_redirector.php';
if (debug){
set_error_handler(function($errno, $errstr, $errfile, $errline) {
debuglog("PHP ERROR [$errno]: $errstr in $errfile on line $errline");
return false; // Let PHP handle as usual (optional)
});
set_exception_handler(function($exception) {
debuglog("PHP EXCEPTION: " . $exception->getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine());
});
}
//------------------------------------------ //------------------------------------------
// Header security - enabled via config // Header security - enabled via config
//------------------------------------------ //------------------------------------------

BIN
api/.DS_Store vendored

Binary file not shown.

View File

@@ -56,7 +56,9 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
$user = $username; $user = $username;
$account = $partnerhierarchy; //string $account = $partnerhierarchy; //string
$current_date = date("Y-m-d"); $service_date = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
$warranty_date = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
$order_send_date = date("Y-m-d");
$input_type = $post_content['type']; $input_type = $post_content['type'];
$testdetails = json_encode($post_content['testdetails']); $testdetails = json_encode($post_content['testdetails']);
$serial = $post_content['sn']; $serial = $post_content['sn'];
@@ -187,9 +189,9 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
// Create equipment when not exist +++++++++++++++++++++++++ // Create equipment when not exist +++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
if ($equipmentCreate == 1 && $total_equipment == 0){ if ($equipmentCreate == 1 && $total_equipment == 0){
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date) VALUES (?,?,?,?,?,?,?,?)'; $sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date,order_send_date) VALUES (?,?,?,?,?,?,?,?,?)';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$current_date,$current_date]); $stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$service_date,$warranty_date,$order_send_date]);
$rowID = $pdo->lastInsertId(); $rowID = $pdo->lastInsertId();
} }
@@ -311,7 +313,7 @@ if (!empty($post_content['sn']) && !empty($post_content['testdetails'])) {
//Update Equipment record //Update Equipment record
$sql = "UPDATE equipment SET service_date = ? $whereclause"; $sql = "UPDATE equipment SET service_date = ? $whereclause";
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([$current_date]); $stmt->execute([$service_date]);
} }
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++

BIN
api/v1/.DS_Store vendored

Binary file not shown.

View File

@@ -84,20 +84,6 @@ switch ($action) {
$communication_check = 0; //Check communication record $communication_check = 0; //Check communication record
$message_box = []; $message_box = [];
$timestamp = date("Y-m-d H:i:s"); $timestamp = date("Y-m-d H:i:s");
// Create history description
$history_description = [
"start_date"=>$timestamp,
"end_date"=>date("Y-m-d", strtotime("+730 days")),
"organization"=>strip_tags(trim($post_content['organization'])),
"phone"=>strip_tags(trim($post_content['phone'])),
"city"=>strip_tags(trim($post_content['city'])),
"country"=>strip_tags(trim($post_content['country'])),
"email_consent"=>strip_tags(trim($post_content['email_consent'])),
"terms_consent"=>strip_tags(trim($post_content['terms_consent']))
];
$description = json_encode($history_description, JSON_UNESCAPED_UNICODE);
// -------------------------------------------- // --------------------------------------------
// Check if multiple serialnumbers are provided // Check if multiple serialnumbers are provided
@@ -108,9 +94,12 @@ switch ($action) {
foreach ($serial_numbers as $sn) { foreach ($serial_numbers as $sn) {
// Get equipment ID based on serial number // Get equipment ID based on serial number
$rowID = getrowID($dbname, 'rowID', 'equipment', 'serialnumber="' . $sn . '"'); $sql = 'SELECT rowID, warranty_date, order_send_date from equipment where serialnumber = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$sn]);
$rowID = $stmt->fetch();
if (!$rowID) { if (!$rowID['rowID']) {
// Serial number not recognized // Serial number not recognized
$message_box[] = $sn . ' - ' . $register_message_1; $message_box[] = $sn . ' - ' . $register_message_1;
continue; continue;
@@ -128,8 +117,45 @@ switch ($action) {
continue; continue;
} }
//define warranty_end_date
$order_send_date = $rowID['order_send_date'] ?? $rowID['warranty_date'];
// Check if order_send_date is available
if (empty($order_send_date)) {
// No valid date found - skip this serial number
$message_box[] = $sn . ' - ' . $register_message_1; // or create a specific message for missing date
continue;
}
// Calculate warranty end date based on eligibility window
$current_date = new DateTime();
$order_date = new DateTime($order_send_date);
$months_diff = $current_date->diff($order_date)->m + ($current_date->diff($order_date)->y * 12);
if ($months_diff <= WARRANTY_ELIGIBILITY_WINDOW) {
// Within eligibility window - apply extended warranty
$warranty_end_date = (clone $order_date)->modify('+' . WARRANTY_EXTENDED_MONTH . ' months')->format('Y-m-d');
} else {
// Outside eligibility window - apply standard warranty
$warranty_end_date = (clone $order_date)->modify('+' . WARRANTY_MONTHS . ' months')->format('Y-m-d');
}
// Not under warranty - process registration // Not under warranty - process registration
$firmware_account_send = 1; $firmware_account_send = 1;
//Create history description
$history_description = [
"start_date"=>$timestamp,
"end_date"=> $warranty_end_date,
"organization"=>strip_tags(trim($post_content['organization'])),
"phone"=>strip_tags(trim($post_content['phone'])),
"city"=>strip_tags(trim($post_content['city'])),
"country"=>strip_tags(trim($post_content['country'])),
"email_consent"=>strip_tags(trim($post_content['email_consent'])),
"terms_consent"=>strip_tags(trim($post_content['terms_consent']))
];
$description = json_encode($history_description, JSON_UNESCAPED_UNICODE);
// Create history entry // Create history entry
$sql = 'INSERT INTO equipment_history (equipmentid, type, description, created, createdby, updatedby) VALUES (?,?,?,?,?,?)'; $sql = 'INSERT INTO equipment_history (equipmentid, type, description, created, createdby, updatedby) VALUES (?,?,?,?,?,?)';
@@ -190,11 +216,11 @@ switch ($action) {
// Update equipment record // Update equipment record
$sql = 'UPDATE equipment SET status = ?, warranty_date = ?, accounthierarchy = ?, updatedby = ? WHERE rowID = ?'; $sql = 'UPDATE equipment SET status = ?, warranty_date = ?, accounthierarchy = ?, updatedby = ? WHERE rowID = ?';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute(['4', $warranty_extended, $partnerhierarchy_json, $username, $rowID['rowID']]); $stmt->execute(['4', $warranty_end_date, $partnerhierarchy_json, $username, $rowID['rowID']]);
// Add warranty to changelog // Add warranty to changelog
$warranty_user = $post_content['email'] ?? 'system'; $warranty_user = $post_content['email'] ?? 'system';
changelog($dbname, 'equipment', $rowID['rowID'], 'Warranty', $warranty_extended, $warranty_user); changelog($dbname, 'equipment', $rowID['rowID'], 'Warranty', $warranty_end_date, $warranty_user);
// Serial number recognized // Serial number recognized
$message_box[] = $sn . ' - ' . $register_message_3; $message_box[] = $sn . ' - ' . $register_message_3;

BIN
api/v2/.DS_Store vendored

Binary file not shown.

View File

@@ -294,8 +294,9 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
FROM products_software_upgrade_paths pup FROM products_software_upgrade_paths pup
JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID
WHERE pup.to_version_id = ? WHERE pup.to_version_id = ?
AND LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ? AND (LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
AND pup.is_active = 1'; OR pup.from_version_id = 9999999)
AND pup.is_active = 1';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([$version['version_id'], $current_sw_version]); $stmt->execute([$version['version_id'], $current_sw_version]);
$upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC); $upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC);

View File

@@ -332,26 +332,28 @@ if (isset($criterias['sn']) && $criterias['sn'] != ''){
} else { } else {
//Part of an upgrade path system //Part of an upgrade path system
//Only show if there's an explicit path FROM current version TO this version //Only show if there's an explicit path FROM current version TO this version
// OR a wildcard path (from_version_id = 9999999)
$sql = 'SELECT pup.price, pup.currency $sql = 'SELECT pup.price, pup.currency
FROM products_software_upgrade_paths pup FROM products_software_upgrade_paths pup
JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID JOIN products_software_versions from_ver ON pup.from_version_id = from_ver.rowID
WHERE pup.to_version_id = ? WHERE pup.to_version_id = ?
AND LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ? AND (LOWER(TRIM(LEADING "0" FROM from_ver.version)) = ?
AND pup.is_active = 1'; OR pup.from_version_id = 9999999)
AND pup.is_active = 1';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([$version['version_id'], $current_sw_version]); $stmt->execute([$version['version_id'], $current_sw_version]);
$upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC); $upgrade_path = $stmt->fetch(PDO::FETCH_ASSOC);
if ($upgrade_path) { if ($upgrade_path) {
//Valid upgrade path found FROM current version //Valid upgrade path found FROM current version or wildcard
$show_version = true; $show_version = true;
$final_price = $upgrade_path['price'] ?? '0.00'; $final_price = $upgrade_path['price'] ?? '0.00';
$final_currency = $upgrade_path['currency'] ?? ''; $final_currency = $upgrade_path['currency'] ?? '';
$decision_reason = 'Showing - found upgrade path FROM current (' . $current_sw_version . ') with price: ' . $final_price . ' ' . $final_currency; $decision_reason = 'Showing - found upgrade path FROM current (' . $current_sw_version . ') or wildcard with price: ' . $final_price . ' ' . $final_currency;
} else { } else {
$decision_reason = 'Skipped - has upgrade paths but none FROM current version (' . $current_sw_version . ')'; $decision_reason = 'Skipped - has upgrade paths but none FROM current version (' . $current_sw_version . ') or wildcard';
} }
//If no path from current version exists, don't show (show_version stays false) //If no path from current version or wildcard exists, don't show (show_version stays false)
} }
} }
} }

View File

@@ -148,9 +148,9 @@ if ($command == 'update'){
//RESET WARRANTY AND SERVICE DATES WHEN STATUS IS CHANGED TO SEND(3) //RESET WARRANTY AND SERVICE DATES WHEN STATUS IS CHANGED TO SEND(3)
if (isset($post_content['status']) && $post_content['status'] == 3 && $equipment_data['status'] != 3) if (isset($post_content['status']) && $post_content['status'] == 3 && $equipment_data['status'] != 3)
{ {
$post_content['service_date'] = $date; $post_content['service_date'] = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
$post_content['warranty_date'] = $date; $post_content['warranty_date'] = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
$post_content['order_send_date'] = $date;
} }
//UPDATE CHANGELOG BASED ON STATUS CHANGE //UPDATE CHANGELOG BASED ON STATUS CHANGE
if (isset($post_content['status']) && $post_content['status'] != $equipment_data['status']) if (isset($post_content['status']) && $post_content['status'] != $equipment_data['status'])
@@ -188,8 +188,15 @@ elseif ($command == 'insert'){
$post_content['created'] = $date; $post_content['created'] = $date;
$post_content['createdby'] = $username; $post_content['createdby'] = $username;
$post_content['accounthierarchy'] = $accounthierarchy; $post_content['accounthierarchy'] = $accounthierarchy;
$post_content['service_date'] = $date; $post_content['service_date'] = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
$post_content['warranty_date'] = $date; $post_content['warranty_date'] = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
if (isset($post_content['status']) && $post_content['status'] == 3)
{
$post_content['order_send_date'] = $date;
}
} }
else { else {
//do nothing //do nothing

View File

@@ -44,12 +44,16 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
$updateObject_visual = 0; //update visual inspection object $updateObject_visual = 0; //update visual inspection object
$sendServiceReport = 0; //send service report via email $sendServiceReport = 0; //send service report via email
$transfercartest = 0; //Update cartest table with incoming data $transfercartest = 0; //Update cartest table with incoming data
$create_software_license = 0; //Create software license
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
//SET DEFAULT PARAMETERS //SET DEFAULT PARAMETERS
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
$user = $username; $user = $username;
$account = $partnerhierarchy; //string $account = $partnerhierarchy; //string
$current_date = date("Y-m-d"); $service_date = date("Y-m-d", strtotime("+" . SERVICE_MONTHS . " months"));
$warranty_date = date("Y-m-d", strtotime("+" . WARRANTY_MONTHS . " months"));
$order_send_date = date("Y-m-d");
$input_type = $post_content['type']; $input_type = $post_content['type'];
$testdetails = json_encode($post_content['payload']); $testdetails = json_encode($post_content['payload']);
$serial = $post_content['sn']; $serial = $post_content['sn'];
@@ -146,6 +150,11 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
$transfercartest = 1; $transfercartest = 1;
break; break;
case 12: //customer_consent
$historytype = 'Customer_consent';
$create_software_license = 1;
break;
case 'firmware': //update from Portal case 'firmware': //update from Portal
$historytype = $HistoryType_2; $historytype = $HistoryType_2;
$equipmentUpdate = 1; $equipmentUpdate = 1;
@@ -164,14 +173,14 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
//Connect to DB //Connect to DB
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
//Get whereclause based on serialnumber //Get whereclause based on serialnumber
$whereclause = checkSerial($serial); $whereclause = checkSerial($serial);
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
//CHECK if EQUIPMENT EXISTS //CHECK if EQUIPMENT EXISTS
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
$sql = "SELECT count(rowID) as total, rowID FROM equipment $whereclause"; $sql = "SELECT count(rowID) as total, rowID, hw_version FROM equipment $whereclause";
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute(); $stmt->execute();
$total = $stmt->fetchAll(PDO::FETCH_ASSOC); $total = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -182,9 +191,9 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
// Create equipment when not exist +++++++++++++++++++++++++ // Create equipment when not exist +++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
if ($equipmentCreate == 1 && $total_equipment == 0){ if ($equipmentCreate == 1 && $total_equipment == 0){
$sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date) VALUES (?,?,?,?,?,?,?,?)'; $sql = 'INSERT INTO equipment (productrowid,created,createdby,status,accounthierarchy,serialnumber,service_date,warranty_date,order_send_date) VALUES (?,?,?,?,?,?,?,?,?)';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$current_date,$current_date]); $stmt->execute([$productrowid,$date,$user,$status0,$account,$serial,$service_date,$warranty_date,$order_send_date]);
$rowID = $pdo->lastInsertId(); $rowID = $pdo->lastInsertId();
} }
@@ -305,7 +314,7 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
//Update Equipment record //Update Equipment record
$sql = "UPDATE equipment SET service_date = ? $whereclause"; $sql = "UPDATE equipment SET service_date = ? $whereclause";
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([$current_date]); $stmt->execute([$service_date]);
} }
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -357,6 +366,49 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
if ($transfercartest == 1){ if ($transfercartest == 1){
convertCartest(); convertCartest();
} }
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
// create software license ++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
if ($create_software_license == 1){
// Generate unique license key
$license_key = generateUniqueLicenseKey();
$sw_version_consent = strtolower($post_content['testdetails']['logdetails']['FW'] ?? '');// version_id
$eq_version_hw = strtolower($rowID['hw_version'] ?? '');
//GET VERSION_ID FROM VERSION TABLE
$sql = 'SELECT rowID FROM products_software_versions WHERE version = ? and hw_version = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$sw_version_consent, $eq_version_hw]);
$version_row = $stmt->fetch(PDO::FETCH_ASSOC);
//GET VERSION_ID or use WILDCARD
$sw_version_consent = $version_row['rowID'] ?? '9999999';
// Create license
$sql = 'INSERT INTO products_software_licenses
(version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, created, createdby)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
$sw_version_consent,
1, // license_type (1 = upgrade)
$license_key,
1, // status = active
date('Y-m-d H:i:s'),
'2099-12-31 23:59:59', // effectively permanent
'Customer_consent',
date('Y-m-d H:i:s'),
$user
]);
// Update equipment.sw_version_license
$sql = 'UPDATE equipment SET sw_version_license = ? WHERE rowID = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$license_key, $rowID]);
}
} }
else else
{ {

View File

@@ -25,6 +25,11 @@ $user_data = $post_content['user_data'] ?? [];
// Read payment_provider from top level first, then fallback to user_data // Read payment_provider from top level first, then fallback to user_data
$payment_provider = $post_content['payment_provider'] ?? $user_data['payment_provider'] ?? 'mollie'; $payment_provider = $post_content['payment_provider'] ?? $user_data['payment_provider'] ?? 'mollie';
// Extract tax information from user_data (sent from frontend)
$item_price = $user_data['item_price'] ?? null; // Price without VAT
$tax_amount = $user_data['tax_amount'] ?? 0; // VAT amount
$payment_amount = $user_data['payment_amount'] ?? null; // Total including VAT
//+++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 1: Get equipment data from serial_number // STEP 1: Get equipment data from serial_number
//+++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -165,8 +170,17 @@ if (debug) {
// STEP 7: Create payment based on provider // STEP 7: Create payment based on provider
//+++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++
try { try {
// Use payment_amount (with tax) if provided, otherwise use final_price
$amount_to_charge = $payment_amount ? (float)$payment_amount : (float)$final_price;
// Format price (must be string with 2 decimals) // Format price (must be string with 2 decimals)
$formatted_price = number_format((float)$final_price, 2, '.', ''); $formatted_price = number_format($amount_to_charge, 2, '.', '');
if (debug) {
debuglog("DEBUG: Item Price (excl. VAT): " . ($item_price ?? $final_price));
debuglog("DEBUG: Tax Amount: " . $tax_amount);
debuglog("DEBUG: Total Amount (incl. VAT): " . $amount_to_charge);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP 7A: Generate transaction ID BEFORE creating payment // STEP 7A: Generate transaction ID BEFORE creating payment
@@ -252,7 +266,7 @@ try {
throw new Exception("No approval URL received from PayPal"); throw new Exception("No approval URL received from PayPal");
} }
$payment_method_id = 1; // PayPal $payment_method_id = 3; // PayPal
$payment_metadata = 'paypal_order_id'; $payment_metadata = 'paypal_order_id';
} else { } else {
@@ -291,7 +305,7 @@ try {
debuglog("DEBUG: Checkout URL: $checkout_url"); debuglog("DEBUG: Checkout URL: $checkout_url");
} }
$payment_method_id = 0; // Mollie $payment_method_id = 1; // Mollie
$payment_metadata = 'mollie_payment_id'; $payment_metadata = 'mollie_payment_id';
} }
@@ -313,13 +327,14 @@ try {
// BUILD UP PARTNERHIERARCHY FROM USER // BUILD UP PARTNERHIERARCHY FROM USER
$partner_product = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE); $partner_product = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE);
$sql = 'INSERT INTO transactions (txn_id, payment_amount, payment_status, payer_email, first_name, last_name, $sql = 'INSERT INTO transactions (txn_id, payment_amount, tax_amount, payment_status, payer_email, first_name, last_name,
address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, accounthierarchy, created) address_street, address_city, address_state, address_zip, address_country, account_id, payment_method, accounthierarchy, created)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([ $stmt->execute([
$txn_id, $txn_id,
$final_price, $amount_to_charge, // Total amount including tax
$tax_amount, // Tax amount
0, // 0 = pending 0, // 0 = pending
$user_data['email'] ?? '', $user_data['email'] ?? '',
$first_name, $first_name,
@@ -348,13 +363,16 @@ try {
$payment_metadata => $payment_id // Store payment provider ID $payment_metadata => $payment_id // Store payment provider ID
], JSON_UNESCAPED_UNICODE); ], JSON_UNESCAPED_UNICODE);
// Use item_price (without VAT) if provided, otherwise use final_price
$item_price_to_store = $item_price ? (float)$item_price : (float)$final_price;
$sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created) $sql = 'INSERT INTO transactions_items (txn_id, item_id, item_price, item_quantity, item_options, created)
VALUES (?, ?, ?, ?, ?, ?)'; VALUES (?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([ $stmt->execute([
$transaction_id, $transaction_id,
$version_id, $version_id,
$final_price, $item_price_to_store, // Price without VAT
1, 1,
$item_options, $item_options,
date('Y-m-d H:i:s') date('Y-m-d H:i:s')

BIN
assets/.DS_Store vendored

Binary file not shown.

View File

@@ -1264,8 +1264,10 @@ function ioServer($api_call, $data){
$http_status = curl_getinfo($curl) ?? '200'; $http_status = curl_getinfo($curl) ?? '200';
curl_close($curl); curl_close($curl);
if(debug){
debuglog($date." - ioServer: URL=$url, HTTP Code=$http_status, Response=" . substr($resp, 0, 500) . (strlen($resp) > 500 ? '...' : '')); if (debug) {
$resp_log = $date . " - ioServer: URL=$url, HTTP Code= ". ($http_status['http_code'] ?? 'unknown') . ", Response=" . substr($resp, 0, 500) . (strlen($resp) > 500 ? '...' : '');
debuglog(json_encode($resp_log));
} }
//Check If errorcode is returned //Check If errorcode is returned
@@ -1728,33 +1730,38 @@ function getPartnerID($str){
// overview Indicators // overview Indicators
//------------------------------------------ //------------------------------------------
function overviewIndicators($warranty, $service, $sw_version, $sw_version_latest){ function overviewIndicators($warranty, $service, $sw_version, $sw_version_latest){
include dirname(__FILE__,2).'/settings/settings_redirector.php';
include dirname(__FILE__,2).'/settings/systemfirmware.php'; include dirname(__FILE__,2).'/settings/settings_redirector.php';
$indicator =''; include dirname(__FILE__,2).'/settings/systemfirmware.php';
//In warranty
if (!empty($warranty ) && $warranty > $warrantydate){ $indicator ='';
$indicator .= '<span class="dot" style="background-color: #13b368;">W</span>'; $current_date = date('Y-m-d');
} else {
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">W</span>'; //In warranty
} if (!empty($warranty ) && $warranty >= $current_date){
//Out of Service $indicator .= '<span class="dot" style="background-color: #13b368;">W</span>';
if (!empty($service) && $service < $servicedate){ } else {
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">S</span>'; $indicator .= '<span class="dot" style="background-color: #eb8a0d;">W</span>';
} else { }
$indicator .= '<span class="dot" style="background-color: #13b368;">S</span>'; //Out of Service
} if (!empty($service) && $service >= $current_date){
$indicator .= '<span class="dot" style="background-color: #13b368;">S</span>';
} else {
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">S</span>';
}
//Firmware //Firmware
if (isset($sw_version_latest)){ if (isset($sw_version_latest)){
if($sw_version_latest == 1){ if($sw_version_latest == 1){
$indicator .= '<span class="dot" style="background-color: #13b368;">F</span>'; $indicator .= '<span class="dot" style="background-color: #13b368;">F</span>';
}
else {
if ($sw_version == ''){
$indicator .= '<span class="dot" style="background-color: #13b368;">F</span>';
} else { } else {
if ($sw_version == ''){ $indicator .= '<span class="dot" style="background-color: #eb8a0d;">F</span>';
$indicator .= '<span class="dot" style="background-color: #13b368;">F</span>';
} else {
$indicator .= '<span class="dot" style="background-color: #eb8a0d;">F</span>';
}
} }
}
} }
return $indicator; return $indicator;
@@ -1783,11 +1790,12 @@ function warrantyStatus($input){
} }
$warranty_date_due ='<span class="status">Unknown</span>'; $warranty_date_due ='<span class="status">Unknown</span>';
$current_date = date('Y-m-d');
if (!empty($input) && $input < $warrantydate){
$warranty_date_due = '<span class="status warranty_outdated">'.$warranty_outdated_text.'</span>'; if (!empty($input) && $input >= $current_date){
$warranty_date_due = '<span class="">'.$warranty_recent.' ('.$input.')</span>';
} else { } else {
$warranty_date_due = '<span class="">'.$warranty_recent.' ('.date('Y-m-d', strtotime($input. ' + 365 days')).')</span>'; $warranty_date_due = '<span class="status warranty_outdated">'.$warranty_outdated_text.'</span>';
} }
return $warranty_date_due; return $warranty_date_due;
@@ -1814,13 +1822,15 @@ function serviceStatus($input){
else { else {
include dirname(__FILE__,2).'/settings/translations/translations_US.php'; include dirname(__FILE__,2).'/settings/translations/translations_US.php';
} }
$current_date = date('Y-m-d');
$service_date_due ='<span class="status">Unknown</span>'; $service_date_due ='<span class="status">Unknown</span>';
if (!empty($input) && $input < $servicedate){ if (!empty($input) && $input >= $current_date){
$service_date_due = '<span class="status service_renewal">'.$service_renewal_text.'</span>'; $service_date_due ='<span class="">'.$service_recent.' ('.$input.')</span>';
} else { } else {
$service_date_due ='<span class="">'.$service_recent.' ('.date('Y-m-d', strtotime($input. ' + 365 days')).')</span>'; $service_date_due = '<span class="status service_renewal">'.$service_renewal_text.'</span>';
} }
return $service_date_due; return $service_date_due;
@@ -2976,20 +2986,29 @@ function showlog($object,$objectID){
$stmt->execute([$object,$objectID]); $stmt->execute([$object,$objectID]);
$changes = $stmt->fetchAll(PDO::FETCH_ASSOC); $changes = $stmt->fetchAll(PDO::FETCH_ASSOC);
$view = '<label for="productcode">Changelog</label>'; $view = '<div class="reg-fields">';
foreach($changes as $change){ if ($changes) {
foreach ($changes as $change) {
$object_value = $change['object_value']; $object_value = $change['object_value'];
// Human-readable status
//UPDATE TO HUMANREADABLE STATUS if ($object == 'equipment' && $change['object_field'] == 'status') {
if ($object == 'equipment' && $change['object_field'] == 'status'){ $object_text = 'status' . $change['object_value'] . '_text';
$object_text = 'status'.$change['object_value'].'_text'; if (isset($$object_text)) {
$object_value = $$object_text; $object_value = $$object_text;
}
}
$entry = htmlspecialchars( $object_value . ' - ' . $change['created'] . ' - ' . $change['createdby']);
$view .= ' <div class="reg-field">
<label>'.$change['object_field'].'</label>
<p>'.$entry.'</p>
</div>';
}
} else {
$view .= '<div style="color:#888;font-size:13px;padding:8px;">No changelog entries found.</div>';
} }
$view .= '<input id="name" type="text" value="'.$change['object_field'].' - '.$object_value.' - '.$change['created'].' - '.$change['createdby'].'" readonly>'; $view .= '</div>';
} return $view;
return $view;
} }
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -5577,4 +5596,48 @@ function updateSoftwareLatestFlags($pdo, $version_id, $hw_version) {
$stmt->execute([$version['rowID']]); $stmt->execute([$version['rowID']]);
} }
} }
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Generate Countries File from Taxes API +++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++
function generateCountriesFile($token){
//API call to get all taxes
$api_url = '/v2/taxes';
$response = ioAPIv2($api_url, '', $token);
if(!empty($response)){
//decode the API response
$taxes = json_decode($response, true);
if(!empty($taxes) && is_array($taxes)){
//Build the countries array - id as key, with country name and tax rate
$countries = [];
foreach($taxes as $tax){
$countries[$tax['id']] = [
'country' => $tax['country'] ?? '',
'taxes' => $tax['rate'] ?? 0
];
}
//Generate PHP file content
$fileContent = "<?php\n";
$fileContent .= "// Auto-generated countries file from taxes API\n";
$fileContent .= "// Generated on: " . date('Y-m-d H:i:s') . "\n\n";
$fileContent .= "\$countries = [\n";
foreach($countries as $id => $data){
$fileContent .= " " . $id . " => ['country' => '" . addslashes($data['country']) . "', 'taxes' => " . $data['taxes'] . "],\n";
}
$fileContent .= "];\n";
//Write to settings/countries.php
$filePath = dirname(__FILE__, 2) . '/settings/countries.php';
$result = file_put_contents($filePath, $fileContent);
return ($result !== false);
}
}
return false;
} }

Binary file not shown.

View File

@@ -10,6 +10,26 @@ let deviceVersion = "";
let deviceHwVersion = ""; let deviceHwVersion = "";
let selectedSoftwareUrl = ""; let selectedSoftwareUrl = "";
// Helper function to generate country select options
function generateCountryOptions(selectedCountry = '') {
if (typeof COUNTRIES === 'undefined' || !COUNTRIES) {
return `<option value="">${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}</option>`;
}
// Sort countries alphabetically
const sortedCountries = Object.values(COUNTRIES).sort((a, b) => {
return a.country.localeCompare(b.country);
});
let options = '<option value="">Select country</option>';
sortedCountries.forEach(data => {
const selected = (selectedCountry === data.country) ? 'selected' : '';
options += `<option value="${data.country}" ${selected}>${data.country}</option>`;
});
return options;
}
// Serial port variables (port, writer, textEncoder, writableStreamClosed declared in PHP) // Serial port variables (port, writer, textEncoder, writableStreamClosed declared in PHP)
let reader; let reader;
let readableStreamClosed; let readableStreamClosed;
@@ -528,8 +548,18 @@ async function fetchSoftwareOptions() {
document.getElementById("softwareOptionsContainer").style.display = "block"; document.getElementById("softwareOptionsContainer").style.display = "block";
progressBar("100", "Software options loaded", "#04AA6D"); progressBar("100", "Software options loaded", "#04AA6D");
// Show user info modal immediately // Show user info modal immediately (skip in debug mode)
showUserInfoModal(); if (typeof DEBUG === 'undefined' || !DEBUG) {
showUserInfoModal();
} else {
// In debug mode, reveal software options immediately
const softwareOptions = document.getElementById("softwareOptions");
if (softwareOptions) {
softwareOptions.style.filter = "none";
softwareOptions.style.opacity = "1";
softwareOptions.style.pointerEvents = "auto";
}
}
} catch (error) { } catch (error) {
await logCommunication(`Software options error: ${error.message}`, 'error'); await logCommunication(`Software options error: ${error.message}`, 'error');
@@ -665,7 +695,7 @@ function displaySoftwareOptions(options) {
} else { } else {
priceText.innerHTML = isFree priceText.innerHTML = isFree
? 'Free' ? 'Free'
: `${option.currency || "€"} ${price.toFixed(2)}`; : `${option.currency || "€"} ${price.toFixed(2)} <small style="font-size: 12px; font-weight: 400; color: #888;">(excl. VAT)</small>`;
} }
priceSection.appendChild(priceText); priceSection.appendChild(priceText);
@@ -777,7 +807,9 @@ function showUserInfoModal() {
<input type="text" name="city" id="userInfoCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; margin-bottom: 10px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'"> <input type="text" name="city" id="userInfoCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; margin-bottom: 10px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="text" name="postal" id="userInfoPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'"> <input type="text" name="postal" id="userInfoPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
<input type="text" name="country" id="userInfoCountry" required placeholder="${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}" style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'"> <select name="country" id="userInfoCountry" required style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
${generateCountryOptions()}
</select>
</div> </div>
</div> </div>
@@ -967,7 +999,9 @@ function showFreeInstallModal(option) {
<input type="text" name="city" id="freeInstallCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;"> <input type="text" name="city" id="freeInstallCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="text" name="postal" id="freeInstallPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"> <input type="text" name="postal" id="freeInstallPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<input type="text" name="country" id="freeInstallCountry" required placeholder="${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"> <select name="country" id="freeInstallCountry" required style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
${generateCountryOptions()}
</select>
</div> </div>
</div> </div>
@@ -1045,6 +1079,17 @@ function showPaymentModal(option) {
const price = parseFloat(option.price || 0); const price = parseFloat(option.price || 0);
const currency = option.currency || "€"; const currency = option.currency || "€";
// Format description as bullet points
const formatDescription = (desc) => {
if (!desc) return '';
// Split by bullet points or newlines and filter out empty lines
const lines = desc.split(/[•·\n]/).map(line => line.trim()).filter(line => line.length > 0);
if (lines.length <= 1) return desc; // Return as-is if no multiple lines
return '<ul style="margin: 0; padding-left: 20px; color: #666; font-size: 13px; line-height: 1.6;">' +
lines.map(line => `<li>${line}</li>`).join('') +
'</ul>';
};
// Create modal overlay // Create modal overlay
const modal = document.createElement("div"); const modal = document.createElement("div");
modal.id = "paymentModal"; modal.id = "paymentModal";
@@ -1082,9 +1127,20 @@ function showPaymentModal(option) {
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;"> <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h4 style="margin: 0 0 10px 0; color: #333;">${option.name || "Software Update"}</h4> <h4 style="margin: 0 0 10px 0; color: #333;">${option.name || "Software Update"}</h4>
<p style="margin: 0 0 5px 0; color: #666;">Version: <strong>${option.version || "N/A"}</strong></p> <p style="margin: 0 0 5px 0; color: #666;">Version: <strong>${option.version || "N/A"}</strong></p>
<p style="margin: 0 0 15px 0; color: #666;">${option.description || ""}</p> <div style="margin: 0 0 15px 0;">${formatDescription(option.description)}</div>
<div style="font-size: 24px; font-weight: bold; color: #04AA6D;"> <div id="priceDisplay" style="font-size: 14px; color: #666;">
${currency} ${price.toFixed(2)} <div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>Price (excl. VAT):</span>
<span style="font-weight: 600;">${currency} ${price.toFixed(2)}</span>
</div>
<div id="taxDisplay" style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>VAT:</span>
<span style="font-weight: 600;">-</span>
</div>
<div style="display: flex; justify-content: space-between; padding-top: 10px; border-top: 2px solid #ddd; margin-top: 10px;">
<span style="font-weight: bold;">Total:</span>
<span id="totalDisplay" style="font-size: 24px; font-weight: bold; color: #04AA6D;">${currency} ${price.toFixed(2)}</span>
</div>
</div> </div>
</div> </div>
@@ -1105,7 +1161,9 @@ function showPaymentModal(option) {
<input type="text" name="city" id="paymentCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;"> <input type="text" name="city" id="paymentCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="text" name="postal" id="paymentPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"> <input type="text" name="postal" id="paymentPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<input type="text" name="country" id="paymentCountry" required placeholder="${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"> <select name="country" id="paymentCountry" required style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
${generateCountryOptions()}
</select>
</div> </div>
</div> </div>
@@ -1134,6 +1192,45 @@ function showPaymentModal(option) {
modal.appendChild(modalContent); modal.appendChild(modalContent);
document.body.appendChild(modal); document.body.appendChild(modal);
// Function to calculate and update tax
function updateTaxDisplay() {
const selectedCountry = document.getElementById("paymentCountry").value;
let taxRate = 0;
if (selectedCountry && typeof COUNTRIES !== 'undefined' && COUNTRIES) {
const countryData = Object.values(COUNTRIES).find(c => c.country === selectedCountry);
if (countryData) {
taxRate = parseFloat(countryData.taxes) || 0;
}
}
const taxAmount = price * (taxRate / 100);
const totalAmount = price + taxAmount;
// Update display
const taxDisplay = document.getElementById("taxDisplay");
const totalDisplay = document.getElementById("totalDisplay");
if (taxRate > 0) {
taxDisplay.innerHTML = `
<span>VAT (${taxRate}%):</span>
<span style="font-weight: 600;">${currency} ${taxAmount.toFixed(2)}</span>
`;
} else {
taxDisplay.innerHTML = `
<span>VAT:</span>
<span style="font-weight: 600;">-</span>
`;
}
totalDisplay.textContent = `${currency} ${totalAmount.toFixed(2)}`;
// Store tax info for form submission
modal.taxRate = taxRate;
modal.taxAmount = taxAmount;
modal.totalAmount = totalAmount;
}
// Prefill form with customer data from sessionStorage if available // Prefill form with customer data from sessionStorage if available
const savedCustomerData = sessionStorage.getItem('customerData'); const savedCustomerData = sessionStorage.getItem('customerData');
if (savedCustomerData) { if (savedCustomerData) {
@@ -1144,12 +1241,18 @@ function showPaymentModal(option) {
if (customerData.address) document.getElementById("paymentAddress").value = customerData.address; if (customerData.address) document.getElementById("paymentAddress").value = customerData.address;
if (customerData.city) document.getElementById("paymentCity").value = customerData.city; if (customerData.city) document.getElementById("paymentCity").value = customerData.city;
if (customerData.postal) document.getElementById("paymentPostal").value = customerData.postal; if (customerData.postal) document.getElementById("paymentPostal").value = customerData.postal;
if (customerData.country) document.getElementById("paymentCountry").value = customerData.country; if (customerData.country) {
document.getElementById("paymentCountry").value = customerData.country;
updateTaxDisplay(); // Calculate tax based on saved country
}
} catch (e) { } catch (e) {
console.warn('Error parsing saved customer data:', e); console.warn('Error parsing saved customer data:', e);
} }
} }
// Add event listener to country select to update tax
document.getElementById("paymentCountry").addEventListener('change', updateTaxDisplay);
// Close modal on cancel // Close modal on cancel
document.getElementById("cancelPayment").onclick = () => { document.getElementById("cancelPayment").onclick = () => {
document.body.removeChild(modal); document.body.removeChild(modal);
@@ -1160,15 +1263,15 @@ function showPaymentModal(option) {
e.preventDefault(); e.preventDefault();
const formData = new FormData(e.target); const formData = new FormData(e.target);
const paymentMethod = formData.get("payment_method"); const paymentMethod = formData.get("payment_method");
// Auto-determine payment provider based on payment method // Auto-determine payment provider based on payment method
let paymentProvider = 'mollie'; // default let paymentProvider = 'mollie'; // default
if (paymentMethod === 'paypal') { if (paymentMethod === '3') { // PayPal payment method ID
paymentProvider = 'paypal'; paymentProvider = 'paypal';
} else if (paymentMethod === 'credit_card' || paymentMethod === 'bank_transfer') { } else if (paymentMethod === '1' || paymentMethod === 'bank_transfer') { // Mollie (Credit Card) or Bank Transfer
paymentProvider = 'mollie'; paymentProvider = 'mollie';
} }
const paymentData = { const paymentData = {
name: formData.get("name"), name: formData.get("name"),
email: formData.get("email"), email: formData.get("email"),
@@ -1179,7 +1282,9 @@ function showPaymentModal(option) {
payment_method: paymentMethod, payment_method: paymentMethod,
payment_provider: paymentProvider, payment_provider: paymentProvider,
version_id: option.version_id, version_id: option.version_id,
price: price, item_price: price, // Price without VAT
tax_amount: modal.taxAmount || 0, // Tax amount
payment_amount: modal.totalAmount || price, // Total price including tax
currency: currency currency: currency
}; };

0
custom/morvalwatches/style/VeLiTi-Logo2.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -441,7 +441,7 @@ $view .= '<div class="content-block">
</tr> </tr>
<tr> <tr>
<td style="width:25%;">'.$general_updated.'</td> <td style="width:25%;">'.$general_updated.'</td>
<td>'.getRelativeTime($responses->updated).'</td> <td><a href="#" onclick="openLogModal(); return false;" class="link-with-icon">'.getRelativeTime($responses->updated).'<i class="fa-solid fa-up-right-from-square"></i></a></td>
</tr> </tr>
</table> </table>
</div> </div>
@@ -704,6 +704,32 @@ if ($latest_customer || $latest_warranty) {
'; ';
} }
// Modal for log
echo '
<div id="logModal" class="reg-modal" style="display:none;">
<div class="reg-modal-content" style="min-width:600px;">
<div class="reg-modal-header">
<h3>Log</h3>
<button onclick="closeLogModal()" class="reg-modal-close">&times;</button>
</div>
<div id="logModalBody" style="max-height:400px;overflow:auto;">
<div id="registrationTab" class="reg-tab-content" style="display: block;">
'.showlog("equipment", "'.$responses->equipmentID.'").'
</div>
</div>
</div>
</div>
<script>
function openLogModal() {
document.getElementById("logModal").style.display = "flex";
}
function closeLogModal() {
document.getElementById("logModal").style.display = "none";
}
</script>
';
template_footer() template_footer()
?> ?>

View File

@@ -87,11 +87,12 @@ if (isset($_GET['equipmentID'])) {
$data = json_encode($_POST, JSON_UNESCAPED_UNICODE); $data = json_encode($_POST, JSON_UNESCAPED_UNICODE);
//API call //API call
$responses = ioServer('/v2/equipments', $data); $responses = ioServer('/v2/equipments', $data);
if ($responses === 'NOK'){ if ($responses === 'NOK'){
} else { } else {
header('Location: index.php?page=equipment&equipmentID='.$equipment_ID.'&success_msg=2'); header('Location: index.php?page=equipment&equipmentID='.$equipment_ID.'&success_msg=2');
exit; exit;
} }
} }
} }

View File

@@ -52,6 +52,12 @@ $responses = ioServer($api_url,'');
//Decode Payload //Decode Payload
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;} if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;}
// Redirect if only one equipment is found
if (is_array($responses) && count($responses) === 1 && isset($responses[0]->equipmentID)) {
header('Location: index.php?page=equipment&equipmentID=' . $responses[0]->equipmentID);
exit;
}
//Return QueryTotal from API //Return QueryTotal from API
$total_url = ((!empty($GET_VALUES) && $GET_VALUES !='') ? '&totals=' : 'totals=' ); $total_url = ((!empty($GET_VALUES) && $GET_VALUES !='') ? '&totals=' : 'totals=' );
$api_url = '/v2/equipments/'.$GET_VALUES.$total_url; $api_url = '/v2/equipments/'.$GET_VALUES.$total_url;

View File

@@ -15,9 +15,21 @@ if (debug && debug_id == $_SESSION['id']){
error_reporting(E_ALL); error_reporting(E_ALL);
} }
if (debug){
set_error_handler(function($errno, $errstr, $errfile, $errline) {
debuglog("PHP ERROR [$errno]: $errstr in $errfile on line $errline");
return false;
});
set_exception_handler(function($exception) {
debuglog("PHP EXCEPTION: " . $exception->getMessage() . " in " . $exception->getFile() . " on line " . $exception->getLine());
});
}
//INCLUDE FUNCTIONS AND SETTINGS //INCLUDE FUNCTIONS AND SETTINGS
include dirname(__FILE__).'/assets/functions.php'; include dirname(__FILE__).'/assets/functions.php';
include dirname(__FILE__).'/settings/settings_redirector.php'; include dirname(__FILE__).'/settings/settings_redirector.php';
include_once dirname(__FILE__).'/settings/countries.php';
//===================================== //=====================================
//TRANSLATION FILE LOCATION //TRANSLATION FILE LOCATION

View File

@@ -40,6 +40,9 @@ if ($update_allowed === 1){
if (isset($_POST['generateDealerInformation'])){ if (isset($_POST['generateDealerInformation'])){
generateDealerInformation($_SESSION['userkey']); generateDealerInformation($_SESSION['userkey']);
} }
if (isset($_POST['generateCountriesFile'])){
generateCountriesFile($_SESSION['userkey']);
}
} }
// Handle success messages // Handle success messages
@@ -91,6 +94,10 @@ $view .= '<div class="content-block tab-content active">
<label for="service">GenerateDealerInfo</label> <label for="service">GenerateDealerInfo</label>
<input type="submit" name="generateDealerInformation" style="width: 15%;" value="DealerInfo" class="btn"> <input type="submit" name="generateDealerInformation" style="width: 15%;" value="DealerInfo" class="btn">
</div> </div>
<div class="form responsive-width-100">
<label for="service">Generate Countries File</label>
<input type="submit" name="generateCountriesFile" style="width: 15%;" value="Countries" class="btn">
</div>
</div> </div>
</div>'; </div>';
} }

View File

@@ -186,7 +186,8 @@ $view .= '<div class="content-block">
<div class="form responsive-width-100"> <div class="form responsive-width-100">
<label for="from_version_id"><i class="required">*</i>From Version</label> <label for="from_version_id"><i class="required">*</i>From Version</label>
<select id="from_version_id" name="from_version_id" required> <select id="from_version_id" name="from_version_id" required>
<option value="">Select From Version</option>'; <option value="">Select From Version</option>
<option value="9999999"'. ($path['from_version_id'] == 9999999 ? ' selected' : '') .'>Any Version (*)</option>';
if (!empty($versions)) { if (!empty($versions)) {
foreach ($versions as $ver) { foreach ($versions as $ver) {
// Skip the TO version from FROM dropdown to prevent FROM = TO // Skip the TO version from FROM dropdown to prevent FROM = TO

View File

@@ -9,6 +9,7 @@ $lang = in_array($lang, $supportedLanguages) ? $lang : 'US';
//INCLUDE THE TRANSLATION //INCLUDE THE TRANSLATION
include_once './settings/translations/translations_'.$lang.'.php'; include_once './settings/translations/translations_'.$lang.'.php';
include_once './settings/countries.php';
//========================================= //=========================================
//GET DOMAIN FOR CORRECT STYLING AND SETTINGS //GET DOMAIN FOR CORRECT STYLING AND SETTINGS
@@ -537,7 +538,18 @@ echo'
<input type="text" name="city" form="regForm" placeholder="'.$register_3_city.'" value="'.$register['city'].'" required="" class="form-field"> <input type="text" name="city" form="regForm" placeholder="'.$register_3_city.'" value="'.$register['city'].'" required="" class="form-field">
<label for="country" class="form-label">'.$register_3_country.' *</label> <label for="country" class="form-label">'.$register_3_country.' *</label>
<input type="text" name="country" form="regForm" placeholder="'.$register_3_country.'" value="'.$register['country'].'" required="" class="form-field"> <select name="country" form="regForm" required="" class="form-field">';
// Sort countries alphabetically
usort($countries, function($a, $b) {
return strcmp($a['country'], $b['country']);
});
foreach ($countries as $id => $data) {
$selected = ($register['country'] == $data['country']) ? 'selected' : '';
echo '<option value="' . $data['country'] . '" ' . $selected . '>' . $data['country'] . '</option>';
}
echo '
</select>
<div style="margin-bottom: 15px;"> <div style="margin-bottom: 15px;">
<input type="checkbox" name="email_consent" form="regForm" checked required/>'.$register_3_email_consent.'<br> <input type="checkbox" name="email_consent" form="regForm" checked required/>'.$register_3_email_consent.'<br>

148
settings/countries.php Normal file
View File

@@ -0,0 +1,148 @@
<?php
// Auto-generated countries file from taxes API
// Generated on: 2026-01-16 14:21:38
$countries = [
1 => ['country' => 'Austria', 'taxes' => 20.00],
2 => ['country' => 'Belgium', 'taxes' => 21.00],
3 => ['country' => 'Bulgaria', 'taxes' => 20.00],
4 => ['country' => 'Croatia', 'taxes' => 25.00],
5 => ['country' => 'Cyprus', 'taxes' => 19.00],
6 => ['country' => 'Czech Republic', 'taxes' => 21.00],
7 => ['country' => 'Denmark', 'taxes' => 25.00],
8 => ['country' => 'Estonia', 'taxes' => 24.00],
9 => ['country' => 'Finland', 'taxes' => 25.50],
10 => ['country' => 'France', 'taxes' => 20.00],
11 => ['country' => 'Germany', 'taxes' => 19.00],
12 => ['country' => 'Greece', 'taxes' => 24.00],
13 => ['country' => 'Hungary', 'taxes' => 27.00],
14 => ['country' => 'Ireland', 'taxes' => 23.00],
15 => ['country' => 'Italy', 'taxes' => 22.00],
16 => ['country' => 'Latvia', 'taxes' => 21.00],
17 => ['country' => 'Lithuania', 'taxes' => 21.00],
18 => ['country' => 'Luxembourg', 'taxes' => 16.00],
19 => ['country' => 'Malta', 'taxes' => 18.00],
20 => ['country' => 'Netherlands', 'taxes' => 21.00],
21 => ['country' => 'Poland', 'taxes' => 23.00],
22 => ['country' => 'Portugal', 'taxes' => 23.00],
23 => ['country' => 'Romania', 'taxes' => 19.00],
24 => ['country' => 'Slovakia', 'taxes' => 23.00],
25 => ['country' => 'Slovenia', 'taxes' => 22.00],
26 => ['country' => 'Spain', 'taxes' => 21.00],
27 => ['country' => 'Sweden', 'taxes' => 25.00],
28 => ['country' => 'United Kingdom', 'taxes' => 20.00],
29 => ['country' => 'Switzerland', 'taxes' => 8.10],
30 => ['country' => 'Norway', 'taxes' => 25.00],
31 => ['country' => 'Iceland', 'taxes' => 24.00],
32 => ['country' => 'Albania', 'taxes' => 20.00],
33 => ['country' => 'Serbia', 'taxes' => 20.00],
34 => ['country' => 'North Macedonia', 'taxes' => 18.00],
35 => ['country' => 'Bosnia and Herzegovina', 'taxes' => 17.00],
36 => ['country' => 'Montenegro', 'taxes' => 21.00],
37 => ['country' => 'Moldova', 'taxes' => 20.00],
38 => ['country' => 'Ukraine', 'taxes' => 20.00],
39 => ['country' => 'Belarus', 'taxes' => 20.00],
40 => ['country' => 'Turkey', 'taxes' => 20.00],
41 => ['country' => 'Andorra', 'taxes' => 4.50],
42 => ['country' => 'Australia', 'taxes' => 10.00],
43 => ['country' => 'New Zealand', 'taxes' => 15.00],
44 => ['country' => 'Japan', 'taxes' => 10.00],
45 => ['country' => 'China', 'taxes' => 13.00],
46 => ['country' => 'India', 'taxes' => 18.00],
47 => ['country' => 'South Korea', 'taxes' => 10.00],
48 => ['country' => 'Singapore', 'taxes' => 9.00],
49 => ['country' => 'Indonesia', 'taxes' => 11.00],
50 => ['country' => 'Thailand', 'taxes' => 7.00],
51 => ['country' => 'Vietnam', 'taxes' => 8.00],
52 => ['country' => 'Philippines', 'taxes' => 12.00],
53 => ['country' => 'Malaysia', 'taxes' => 0.00],
54 => ['country' => 'Taiwan', 'taxes' => 5.00],
55 => ['country' => 'Pakistan', 'taxes' => 18.00],
56 => ['country' => 'Bangladesh', 'taxes' => 15.00],
57 => ['country' => 'Sri Lanka', 'taxes' => 18.00],
58 => ['country' => 'Nepal', 'taxes' => 13.00],
59 => ['country' => 'Cambodia', 'taxes' => 10.00],
60 => ['country' => 'Myanmar', 'taxes' => 5.00],
61 => ['country' => 'Laos', 'taxes' => 10.00],
62 => ['country' => 'Mongolia', 'taxes' => 10.00],
63 => ['country' => 'Kazakhstan', 'taxes' => 12.00],
64 => ['country' => 'Uzbekistan', 'taxes' => 12.00],
65 => ['country' => 'Armenia', 'taxes' => 20.00],
66 => ['country' => 'Georgia', 'taxes' => 18.00],
67 => ['country' => 'Azerbaijan', 'taxes' => 18.00],
68 => ['country' => 'Fiji', 'taxes' => 9.00],
69 => ['country' => 'Papua New Guinea', 'taxes' => 10.00],
70 => ['country' => 'Samoa', 'taxes' => 15.00],
71 => ['country' => 'Tonga', 'taxes' => 15.00],
72 => ['country' => 'Vanuatu', 'taxes' => 15.00],
73 => ['country' => 'Bhutan', 'taxes' => 7.00],
74 => ['country' => 'Saudi Arabia', 'taxes' => 15.00],
75 => ['country' => 'United Arab Emirates', 'taxes' => 5.00],
76 => ['country' => 'Bahrain', 'taxes' => 10.00],
77 => ['country' => 'Kuwait', 'taxes' => 0.00],
78 => ['country' => 'Oman', 'taxes' => 5.00],
79 => ['country' => 'Qatar', 'taxes' => 0.00],
80 => ['country' => 'Israel', 'taxes' => 17.00],
81 => ['country' => 'Jordan', 'taxes' => 16.00],
82 => ['country' => 'Lebanon', 'taxes' => 11.00],
83 => ['country' => 'Egypt', 'taxes' => 14.00],
85 => ['country' => 'South Africa', 'taxes' => 15.00],
86 => ['country' => 'Nigeria', 'taxes' => 7.50],
87 => ['country' => 'Kenya', 'taxes' => 16.00],
88 => ['country' => 'Ghana', 'taxes' => 15.00],
89 => ['country' => 'Morocco', 'taxes' => 20.00],
90 => ['country' => 'Tunisia', 'taxes' => 19.00],
91 => ['country' => 'Algeria', 'taxes' => 19.00],
92 => ['country' => 'Egypt', 'taxes' => 14.00],
93 => ['country' => 'Ethiopia', 'taxes' => 15.00],
94 => ['country' => 'Tanzania', 'taxes' => 18.00],
95 => ['country' => 'Uganda', 'taxes' => 18.00],
96 => ['country' => 'Zimbabwe', 'taxes' => 15.00],
97 => ['country' => 'Zambia', 'taxes' => 16.00],
98 => ['country' => 'Botswana', 'taxes' => 14.00],
99 => ['country' => 'Mauritius', 'taxes' => 15.00],
100 => ['country' => 'Namibia', 'taxes' => 15.00],
101 => ['country' => 'Rwanda', 'taxes' => 18.00],
102 => ['country' => 'Senegal', 'taxes' => 18.00],
103 => ['country' => 'Ivory Coast', 'taxes' => 18.00],
104 => ['country' => 'Cameroon', 'taxes' => 19.25],
105 => ['country' => 'Angola', 'taxes' => 14.00],
106 => ['country' => 'Mozambique', 'taxes' => 16.00],
107 => ['country' => 'Madagascar', 'taxes' => 20.00],
108 => ['country' => 'Mali', 'taxes' => 18.00],
109 => ['country' => 'Burkina Faso', 'taxes' => 18.00],
110 => ['country' => 'Niger', 'taxes' => 19.00],
111 => ['country' => 'Benin', 'taxes' => 18.00],
112 => ['country' => 'Togo', 'taxes' => 18.00],
113 => ['country' => 'Guinea', 'taxes' => 18.00],
114 => ['country' => 'Malawi', 'taxes' => 16.50],
115 => ['country' => 'Gabon', 'taxes' => 18.00],
116 => ['country' => 'Mauritania', 'taxes' => 16.00],
117 => ['country' => 'Lesotho', 'taxes' => 15.00],
118 => ['country' => 'Eswatini', 'taxes' => 15.00],
119 => ['country' => 'Liberia', 'taxes' => 18.00],
120 => ['country' => 'Canada', 'taxes' => 5.00],
121 => ['country' => 'United States', 'taxes' => 10.00],
122 => ['country' => 'Mexico', 'taxes' => 16.00],
123 => ['country' => 'Argentina', 'taxes' => 21.00],
124 => ['country' => 'Brazil', 'taxes' => 17.00],
125 => ['country' => 'Chile', 'taxes' => 19.00],
126 => ['country' => 'Colombia', 'taxes' => 19.00],
127 => ['country' => 'Peru', 'taxes' => 18.00],
128 => ['country' => 'Ecuador', 'taxes' => 15.00],
129 => ['country' => 'Uruguay', 'taxes' => 22.00],
130 => ['country' => 'Paraguay', 'taxes' => 10.00],
131 => ['country' => 'Bolivia', 'taxes' => 13.00],
132 => ['country' => 'Venezuela', 'taxes' => 16.00],
133 => ['country' => 'Costa Rica', 'taxes' => 13.00],
134 => ['country' => 'Panama', 'taxes' => 7.00],
135 => ['country' => 'Guatemala', 'taxes' => 12.00],
136 => ['country' => 'Honduras', 'taxes' => 15.00],
137 => ['country' => 'El Salvador', 'taxes' => 13.00],
138 => ['country' => 'Nicaragua', 'taxes' => 15.00],
139 => ['country' => 'Dominican Republic', 'taxes' => 18.00],
140 => ['country' => 'Jamaica', 'taxes' => 15.00],
141 => ['country' => 'Trinidad and Tobago', 'taxes' => 12.50],
142 => ['country' => 'Barbados', 'taxes' => 17.50],
143 => ['country' => 'Bahamas', 'taxes' => 10.00],
];

View File

@@ -22,6 +22,24 @@ $payment_return = isset($_GET['order_id']) ? $_GET['order_id'] : null;
$payment_return_status = isset($_GET['payment_return']) ? $_GET['payment_return'] : null; $payment_return_status = isset($_GET['payment_return']) ? $_GET['payment_return'] : null;
$paypal_token = isset($_GET['token']) ? $_GET['token'] : null; // PayPal returns with ?token= $paypal_token = isset($_GET['token']) ? $_GET['token'] : null; // PayPal returns with ?token=
// Handle payment cancellation - mark order as cancelled
if ($payment_return && $payment_return_status === 'cancelled') {
try {
$pdo = dbConnect($dbname);
$sql = 'UPDATE transactions SET payment_status = 999 WHERE txn_id = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$payment_return]);
if (debug) {
debuglog("Payment cancelled - Order ID: {$payment_return} marked as cancelled (999)");
}
} catch (Exception $e) {
if (debug) {
debuglog("Error marking order as cancelled: " . $e->getMessage());
}
}
}
// Handle PayPal return - capture the order directly // Handle PayPal return - capture the order directly
if ($paypal_token && $payment_return) { if ($paypal_token && $payment_return) {
try { try {
@@ -134,8 +152,23 @@ if ($payment_return && $payment_return_status) {
// Auto-refresh every 3 seconds to check payment status // Auto-refresh every 3 seconds to check payment status
setTimeout(function() { location.reload(); }, 3000); setTimeout(function() { location.reload(); }, 3000);
</script>'; </script>';
} else if ($transaction['payment_status'] == 999) {
// Payment cancelled
$payment_modal = '
<div id="paymentModal" class="modal" style="display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
<div class="modal-content" style="background: white; border-radius: 12px; max-width: 500px; margin: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); position: relative;">
<span class="close" onclick="closePaymentModal()" style="position: absolute; top: 15px; right: 20px; font-size: 28px; font-weight: bold; color: #999; cursor: pointer;">&times;</span>
<div style="text-align: center; padding: 40px 30px;">
<i class="fa-solid fa-times-circle" style="font-size: 64px; color: #ffc107; margin-bottom: 20px;"></i>
<h2 style="color: #856404; margin-bottom: 15px;">Payment Cancelled</h2>
<p style="margin-bottom: 10px; color: #333;">You cancelled the payment. The order has been cancelled.</p>
<p style="font-size: 12px; color: #666; margin-bottom: 25px;">Order ID: '.htmlspecialchars($payment_return).'</p>
<button onclick="closePaymentModal()" class="btn" style="padding: 12px 30px;">Close</button>
</div>
</div>
</div>';
} else { } else {
// Payment failed/cancelled // Payment failed/expired
$payment_modal = ' $payment_modal = '
<div id="paymentModal" class="modal" style="display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;"> <div id="paymentModal" class="modal" style="display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
<div class="modal-content" style="background: white; border-radius: 12px; max-width: 500px; margin: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); position: relative;"> <div class="modal-content" style="background: white; border-radius: 12px; max-width: 500px; margin: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); position: relative;">
@@ -297,6 +330,9 @@ echo '
var TRANS_USER_INFO_REQUIRED = "'.($user_information_required ?? 'User Information Required').'"; var TRANS_USER_INFO_REQUIRED = "'.($user_information_required ?? 'User Information Required').'";
var TRANS_USER_INFO_DESCRIPTION = "'.($user_information_description ?? 'Please provide your information to continue with software updates').'"; var TRANS_USER_INFO_DESCRIPTION = "'.($user_information_description ?? 'Please provide your information to continue with software updates').'";
var TRANS_CONTINUE = "'.($general_continue ?? 'Continue').'"; var TRANS_CONTINUE = "'.($general_continue ?? 'Continue').'";
// Countries data
var COUNTRIES = '.json_encode($countries ?? []).';
var port, textEncoder, writableStreamClosed, writer, historyIndex = -1; var port, textEncoder, writableStreamClosed, writer, historyIndex = -1;
const lineHistory = []; const lineHistory = [];