Fix CAP calculation ad reporting, limit cashier cancel ability

parent b35eb9f0
...@@ -4,12 +4,22 @@ ...@@ -4,12 +4,22 @@
echo "🚀 MbetterClient Build Script" echo "🚀 MbetterClient Build Script"
echo "=============================" echo "============================="
USE_BUILT_PYQT6=false
# Check if Python 3 is available # Check if Python 3 is available
if ! command -v python3 &> /dev/null; then if ! command -v python3 &> /dev/null; then
echo "❌ Python 3 is required but not installed." echo "❌ Python 3 is required but not installed."
exit 1 exit 1
fi fi
# Check if built PyQt6 directory exists
if [ -d "pyqt6_built" ]; then
echo "📦 Built PyQt6 found in pyqt6_built/. Using local build..."
USE_BUILT_PYQT6=true
else
USE_BUILT_PYQT6=false
fi
# Check if virtual environment exists # Check if virtual environment exists
if [ ! -d "venv" ]; then if [ ! -d "venv" ]; then
echo "⚠️ Virtual environment not found. Creating one..." echo "⚠️ Virtual environment not found. Creating one..."
...@@ -20,12 +30,22 @@ fi ...@@ -20,12 +30,22 @@ fi
echo "🔧 Activating virtual environment..." echo "🔧 Activating virtual environment..."
source venv/bin/activate source venv/bin/activate
# Set paths for built PyQt6 if available
if $USE_BUILT_PYQT6; then
export PYTHONPATH="pyqt6_built/usr/lib/python3/dist-packages:$PYTHONPATH"
export LD_LIBRARY_PATH="pyqt6_built/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
fi
# Install/upgrade dependencies # Install/upgrade dependencies
echo "📦 Installing dependencies..." echo "📦 Installing dependencies..."
if [ -n "$VIRTUAL_ENV" ]; then if [ -n "$VIRTUAL_ENV" ]; then
echo " 📦 Using virtual environment: $VIRTUAL_ENV" echo " 📦 Using virtual environment: $VIRTUAL_ENV"
pip install --upgrade pip pip install --upgrade pip
pip install -r requirements.txt if $USE_BUILT_PYQT6; then
pip install -r requirements.txt --ignore-installed PyQt6 PyQt6-WebEngine
else
pip install -r requirements.txt
fi
# Verify critical package installations # Verify critical package installations
echo " 🔍 Verifying critical package installations..." echo " 🔍 Verifying critical package installations..."
......
#!/bin/bash
# Build PyQt6 without SSSE4.2 and POPCNT requirements using QEMU with restricted CPU
echo "🚀 Building PyQt6 with restricted CPU using QEMU"
echo "================================================"
# Check and install required tools
echo "🔧 Checking for required tools..."
if ! command -v sbuild-qemu-create &> /dev/null || ! command -v sbuild &> /dev/null; then
echo " 📦 Installing sbuild and qemu..."
sudo apt-get update
sudo apt-get install -y sbuild sbuild-qemu qemu-system-x86 qemu-user qemu-user-binfmt qemu devuan-archive-keyring
else
echo " ✅ Required tools are available"
fi
# Create symlink for qemu command
#if [ -f /usr/bin/qemu-system-x86_64 ]; then
# sudo ln -sf /usr/bin/qemu-system-x86_64 /usr/bin/qemu
#el
if [ -f /usr/bin/qemu-amd64 ]; then
sudo ln -sf /usr/bin/qemu-amd64 /usr/bin/qemu
elif [ -f /usr/bin/qemu-x86_64 ]; then
sudo ln -sf /usr/bin/qemu-x86_64 /usr/bin/qemu
else
echo " ⚠️ No suitable QEMU binary found"
fi
IMG="/srv/sbuild/qemu/sid-amd64.img"
PACKAGE="python3-pyqt6"
# 1. Create QEMU image if it doesn't exist
if [ ! -f "$IMG" ]; then
echo "📦 Creating QEMU image..."
sudo mkdir -p /srv/sbuild/qemu/
sudo sbuild-qemu-create -o "$IMG" daedalus http://deb.devuan.org/
else
echo "📦 QEMU image already exists"
fi
# Update the QEMU image
echo "🔄 Updating QEMU image..."
sudo sbuild-qemu-update "$IMG"
# --- Configuration ---
IMG_PATH=$IMG
MOUNT_DIR="/mnt/sbuild_img"
CHROOT_NAME="temp-img-chroot"
CPU_FLAG="Penryn"
PACKAGE_build="pyqt6"
if [ -z "$PACKAGE" ]; then
echo "Usage: $0 <package_name>"
exit 1
fi
# 1. Clean up any previous failed mounts
sudo umount -R $MOUNT_DIR 2>/dev/null || true
sudo qemu-nbd --disconnect /dev/nbd0 2>/dev/null || true
# 2. Mount the image
echo "Connecting image..."
sudo modprobe nbd
sudo qemu-nbd --connect=/dev/nbd0 "$IMG_PATH"
sleep 1 # Wait for partitions to register
# Create mount point and mount the first partition (adjust p1 if necessary)
sudo mkdir -p $MOUNT_DIR
sudo mount /dev/nbd0p1 $MOUNT_DIR
# 3. Setup QEMU-User environment
echo "Setting up qemu-user-static..."
# Copy the static binary into the image so the chroot can execute it
sudo cp /usr/bin/qemu-x86_64 "$MOUNT_DIR/usr/bin/qemu-x86_64-static"
# Bind mount system paths
for i in /dev /dev/pts /proc /sys /run; do
sudo mount -B $i "$MOUNT_DIR$i"
done
# 4. Create temporary schroot config
echo "Configuring schroot..."
SCHROOT_CONF="/etc/schroot/chroot.d/$CHROOT_NAME"
sudo bash -c "cat > $SCHROOT_CONF" <<EOF
[$CHROOT_NAME]
description=Temporary chroot from $IMG_PATH
directory=$MOUNT_DIR
type=directory
users=$(whoami)
groups=sbuild
root-groups=sbuild
profile=sbuild
EOF
# 5. Run the build with the CPU flag
echo "Starting build with QEMU_CPU=$CPU_FLAG..."
# QEMU_CPU is the environment variable used by qemu-user to set the CPU model
export QEMU_CPU="$CPU_FLAG"
read aaa
# Run sbuild
# --chroot points to the [name] we defined in the .conf file above
sbuild -d unstable --chroot "$CHROOT_NAME" "$PACKAGE_build"
# 6. Cleanup
echo "Cleaning up..."
sudo rm "$SCHROOT_CONF"
sudo umount -R $MOUNT_DIR
sudo qemu-nbd --disconnect /dev/nbd0
sudo rmdir $MOUNT_DIR
echo "Build complete."
# Extract the built package to pyqt6_built directory
if ls ${PACKAGE}_*.deb 1> /dev/null 2>&1; then
echo "📦 Extracting built package to pyqt6_built/..."
mkdir -p pyqt6_built
dpkg -x ${PACKAGE}_*.deb pyqt6_built/
echo "✅ PyQt6 extracted to pyqt6_built/"
else
echo "❌ No .deb file found after build"
fi
echo "✅ PyQt6 build completed!"
# --- Configuration ---
IMG_PATH="/srv/sbuild/qemu/sid-amd64.img"
MOUNT_DIR="/mnt/sbuild_img"
CHROOT_NAME="temp-img-chroot"
CPU_FLAG="Penryn"
PACKAGE=$1
if [ -z "$PACKAGE" ]; then
echo "Usage: $0 <package_name>"
exit 1
fi
# 1. Clean up any previous failed mounts
sudo umount -R $MOUNT_DIR 2>/dev/null || true
sudo qemu-nbd --disconnect /dev/nbd0 2>/dev/null || true
# 2. Mount the image
echo "Connecting image..."
sudo modprobe nbd
sudo qemu-nbd --connect=/dev/nbd0 "$IMG_PATH"
sleep 1 # Wait for partitions to register
# Create mount point and mount the first partition (adjust p1 if necessary)
sudo mkdir -p $MOUNT_DIR
sudo mount /dev/nbd0p1 $MOUNT_DIR
# 3. Setup QEMU-User environment
echo "Setting up qemu-user-static..."
# Copy the static binary into the image so the chroot can execute it
sudo cp /usr/bin/qemu-x86_64 "$MOUNT_DIR/usr/bin/qemu-x86_64-static"
# Bind mount system paths
for i in /dev /dev/pts /proc /sys /run; do
sudo mount -B $i "$MOUNT_DIR$i"
done
# 4. Create temporary schroot config
echo "Configuring schroot..."
SCHROOT_CONF="/etc/schroot/chroot.d/$CHROOT_NAME"
sudo bash -c "cat > $SCHROOT_CONF" <<EOF
[$CHROOT_NAME]
description=Temporary chroot from $IMG_PATH
directory=$MOUNT_DIR
type=directory
users=$(whoami)
groups=sbuild
root-groups=sbuild
profile=sbuild
EOF
# 5. Run the build with the CPU flag
echo "Starting build with QEMU_CPU=$CPU_FLAG..."
# QEMU_CPU is the environment variable used by qemu-user to set the CPU model
export QEMU_CPU="$CPU_FLAG"
# Run sbuild
# --chroot points to the [name] we defined in the .conf file above
sbuild -d testing --chroot "$CHROOT_NAME" "$PACKAGE"
# 6. Cleanup
echo "Cleaning up..."
sudo rm "$SCHROOT_CONF"
sudo umount -R $MOUNT_DIR
sudo qemu-nbd --disconnect /dev/nbd0
sudo rmdir $MOUNT_DIR
echo "Build complete."
#!/bin/bash
# Clean up files created by build_pyqt6.sh
echo "🧹 Cleaning up PyQt6 build artifacts"
echo "===================================="
# Remove QEMU image
IMG="/srv/sbuild/qemu/sid-amd64.img"
if [ -f "$IMG" ]; then
echo "🗑️ Removing QEMU image: $IMG"
sudo rm -f "$IMG"
else
echo "ℹ️ QEMU image not found"
fi
# Remove QEMU directory if empty
QEMU_DIR="/srv/sbuild/qemu"
if [ -d "$QEMU_DIR" ] && [ -z "$(ls -A $QEMU_DIR)" ]; then
echo "🗑️ Removing empty QEMU directory: $QEMU_DIR"
sudo rmdir "$QEMU_DIR"
fi
# Remove extracted PyQt6 directory
if [ -d "pyqt6_built" ]; then
echo "🗑️ Removing extracted PyQt6 directory: pyqt6_built"
rm -rf pyqt6_built
else
echo "ℹ️ pyqt6_built directory not found"
fi
# Remove built .deb files
if ls python3-pyqt6_*.deb 1> /dev/null 2>&1; then
echo "🗑️ Removing built .deb files: python3-pyqt6_*.deb"
rm -f python3-pyqt6_*.deb
else
echo "ℹ️ No python3-pyqt6 .deb files found"
fi
echo "✅ Cleanup completed!"
\ No newline at end of file
This diff is collapsed.
...@@ -789,13 +789,15 @@ class DatabaseManager: ...@@ -789,13 +789,15 @@ class DatabaseManager:
try: try:
session = self.get_session() session = self.get_session()
# Check if admin user already exists by username (more specific than is_admin check) # Check if admin user already exists by username or email
admin_user = session.query(UserModel).filter_by(username='admin').first() admin_user = session.query(UserModel).filter(
(UserModel.username == 'admin') | (UserModel.email == 'admin@mbetterclient.local')
).first()
if not admin_user: if not admin_user:
# Only create if no admin user exists at all # Only create if no admin user exists at all
any_admin = session.query(UserModel).filter_by(is_admin=True).first() any_admin = session.query(UserModel).filter_by(is_admin=True).first()
if not any_admin: if not any_admin:
# Create default admin - migrations should handle this, but fallback just in case # Create default admin - migrations should handle this, but fallback just in case
admin = UserModel( admin = UserModel(
...@@ -804,23 +806,25 @@ class DatabaseManager: ...@@ -804,23 +806,25 @@ class DatabaseManager:
is_admin=True is_admin=True
) )
admin.set_password('admin123') admin.set_password('admin123')
# Set admin role (handle backward compatibility) # Set admin role (handle backward compatibility)
if hasattr(admin, 'set_role'): if hasattr(admin, 'set_role'):
admin.set_role('admin') admin.set_role('admin')
elif hasattr(admin, 'role'): elif hasattr(admin, 'role'):
admin.role = 'admin' admin.role = 'admin'
session.add(admin) session.add(admin)
logger.info("Default admin user created via fallback method (admin/admin123)") logger.info("Default admin user created via fallback method (admin/admin123)")
else: else:
logger.info("Admin users exist, skipping default admin creation") logger.info("Admin users exist, skipping default admin creation")
else: else:
logger.info("Admin user 'admin' already exists, skipping creation") logger.info("Admin user 'admin' or email 'admin@mbetterclient.local' already exists, skipping creation")
# Check if default cashier exists (this should be handled by Migration_007) # Check if default cashier exists (this should be handled by Migration_007)
cashier_user = session.query(UserModel).filter_by(username='cashier').first() cashier_user = session.query(UserModel).filter(
(UserModel.username == 'cashier') | (UserModel.email == 'cashier@mbetterclient.local')
).first()
if not cashier_user: if not cashier_user:
# Create default cashier - migrations should handle this, but fallback just in case # Create default cashier - migrations should handle this, but fallback just in case
cashier = UserModel( cashier = UserModel(
...@@ -829,17 +833,42 @@ class DatabaseManager: ...@@ -829,17 +833,42 @@ class DatabaseManager:
is_admin=False is_admin=False
) )
cashier.set_password('cashier123') cashier.set_password('cashier123')
# Set cashier role (handle backward compatibility) # Set cashier role (handle backward compatibility)
if hasattr(cashier, 'set_role'): if hasattr(cashier, 'set_role'):
cashier.set_role('cashier') cashier.set_role('cashier')
elif hasattr(cashier, 'role'): elif hasattr(cashier, 'role'):
cashier.role = 'cashier' cashier.role = 'cashier'
session.add(cashier) session.add(cashier)
logger.info("Default cashier user created via fallback method (cashier/cashier123)") logger.info("Default cashier user created via fallback method (cashier/cashier123)")
else: else:
logger.info("Cashier user 'cashier' already exists, skipping creation") logger.info("Cashier user 'cashier' or email 'cashier@mbetterclient.local' already exists, skipping creation")
# Check if default normal user exists
normal_user = session.query(UserModel).filter(
(UserModel.username == 'user') | (UserModel.email == 'user@mbetterclient.local')
).first()
if not normal_user:
# Create default normal user
user = UserModel(
username='user',
email='user@mbetterclient.local',
is_admin=False
)
user.set_password('user123')
# Set normal role (handle backward compatibility)
if hasattr(user, 'set_role'):
user.set_role('normal')
elif hasattr(user, 'role'):
user.role = 'normal'
session.add(user)
logger.info("Default normal user created via fallback method (user/user123)")
else:
logger.info("Normal user 'user' or email 'user@mbetterclient.local' already exists, skipping creation")
session.commit() session.commit()
......
...@@ -355,10 +355,10 @@ class Migration_007_CreateDefaultCashierUser(DatabaseMigration): ...@@ -355,10 +355,10 @@ class Migration_007_CreateDefaultCashierUser(DatabaseMigration):
"""Create default cashier user""" """Create default cashier user"""
try: try:
with db_manager.engine.connect() as conn: with db_manager.engine.connect() as conn:
# Check if cashier user already exists # Check if cashier user already exists (by username or email)
result = conn.execute(text("SELECT COUNT(*) FROM users WHERE username = 'cashier'")) result = conn.execute(text("SELECT COUNT(*) FROM users WHERE username = 'cashier' OR email = 'cashier@mbetterclient.local'"))
cashier_count = result.scalar() cashier_count = result.scalar()
if cashier_count == 0: if cashier_count == 0:
# No cashier user exists, create default cashier # No cashier user exists, create default cashier
import hashlib import hashlib
......
...@@ -4767,7 +4767,7 @@ def get_cashier_bet_details(bet_id): ...@@ -4767,7 +4767,7 @@ def get_cashier_bet_details(bet_id):
def cancel_cashier_bet(bet_id): def cancel_cashier_bet(bet_id):
"""Cancel a bet and all its details (cashier)""" """Cancel a bet and all its details (cashier)"""
try: try:
from ..database.models import BetModel, BetDetailModel from ..database.models import BetModel, BetDetailModel, MatchModel
bet_uuid = str(bet_id) bet_uuid = str(bet_id)
session = api_bp.db_manager.get_session() session = api_bp.db_manager.get_session()
...@@ -4777,8 +4777,16 @@ def cancel_cashier_bet(bet_id): ...@@ -4777,8 +4777,16 @@ def cancel_cashier_bet(bet_id):
if not bet: if not bet:
return jsonify({"error": "Bet not found"}), 404 return jsonify({"error": "Bet not found"}), 404
# Check if bet can be cancelled (only pending bets) # Get bet details
bet_details = session.query(BetDetailModel).filter_by(bet_id=bet_uuid).all() bet_details = session.query(BetDetailModel).filter_by(bet_id=bet_uuid).all()
# Check if any match has already started
match_ids = [detail.match_id for detail in bet_details]
matches = session.query(MatchModel).filter(MatchModel.id.in_(match_ids)).all()
if any(match.status in ['ingame', 'done'] for match in matches):
return jsonify({"error": "Cannot cancel bet because one or more matches have already started"}), 400
# Check if bet can be cancelled (only pending bets)
if any(detail.result != 'pending' for detail in bet_details): if any(detail.result != 'pending' for detail in bet_details):
return jsonify({"error": "Cannot cancel bet with non-pending results"}), 400 return jsonify({"error": "Cannot cancel bet with non-pending results"}), 400
...@@ -5504,13 +5512,15 @@ def get_daily_reports_summary(): ...@@ -5504,13 +5512,15 @@ def get_daily_reports_summary():
bets = bets_query.all() bets = bets_query.all()
logger.info(f"Found {len(bets)} bets for daily summary") logger.info(f"Found {len(bets)} bets for daily summary")
# Calculate totals # Calculate totals (excluding cancelled bets)
total_payin = 0.0 total_payin = 0.0
total_bets = 0 total_bets = 0
for bet in bets: for bet in bets:
# Get bet details for this bet # Get bet details for this bet (excluding cancelled bets)
bet_details = session.query(BetDetailModel).filter_by(bet_id=bet.uuid).all() bet_details = session.query(BetDetailModel).filter_by(
bet_id=bet.uuid
).filter(BetDetailModel.result != 'cancelled').all()
bet_total = sum(float(detail.amount) for detail in bet_details) bet_total = sum(float(detail.amount) for detail in bet_details)
total_payin += bet_total total_payin += bet_total
total_bets += len(bet_details) total_bets += len(bet_details)
...@@ -5576,10 +5586,11 @@ def get_match_reports(): ...@@ -5576,10 +5586,11 @@ def get_match_reports():
logger.info(f"Querying match reports for local date {date_param}: UTC range {start_datetime} to {end_datetime}") logger.info(f"Querying match reports for local date {date_param}: UTC range {start_datetime} to {end_datetime}")
# Get all matches that had bets on this day # Get all matches that had bets on this day (excluding cancelled bets)
bet_details_query = session.query(BetDetailModel).join(BetModel).filter( bet_details_query = session.query(BetDetailModel).join(BetModel).filter(
BetModel.bet_datetime >= start_datetime, BetModel.bet_datetime >= start_datetime,
BetModel.bet_datetime <= end_datetime BetModel.bet_datetime <= end_datetime,
BetDetailModel.result != 'cancelled'
) )
# Group by match_id and calculate statistics # Group by match_id and calculate statistics
...@@ -5789,7 +5800,7 @@ def download_excel_report(): ...@@ -5789,7 +5800,7 @@ def download_excel_report():
ws_summary['A1'].font = Font(bold=True, size=16) ws_summary['A1'].font = Font(bold=True, size=16)
ws_summary.merge_cells('A1:E1') ws_summary.merge_cells('A1:E1')
# Get daily summary data # Get daily summary data (excluding cancelled bets)
bets_query = session.query(BetModel).filter( bets_query = session.query(BetModel).filter(
BetModel.bet_datetime >= start_datetime, BetModel.bet_datetime >= start_datetime,
BetModel.bet_datetime <= end_datetime BetModel.bet_datetime <= end_datetime
...@@ -5799,7 +5810,9 @@ def download_excel_report(): ...@@ -5799,7 +5810,9 @@ def download_excel_report():
total_payin = 0.0 total_payin = 0.0
total_bets = 0 total_bets = 0
for bet in bets: for bet in bets:
bet_details = session.query(BetDetailModel).filter_by(bet_id=bet.uuid).all() bet_details = session.query(BetDetailModel).filter_by(
bet_id=bet.uuid
).filter(BetDetailModel.result != 'cancelled').all()
bet_total = sum(float(detail.amount) for detail in bet_details) bet_total = sum(float(detail.amount) for detail in bet_details)
total_payin += bet_total total_payin += bet_total
total_bets += len(bet_details) total_bets += len(bet_details)
...@@ -5851,10 +5864,11 @@ def download_excel_report(): ...@@ -5851,10 +5864,11 @@ def download_excel_report():
cell.border = border cell.border = border
cell.alignment = Alignment(horizontal='center') cell.alignment = Alignment(horizontal='center')
# Get match data # Get match data (excluding cancelled bets)
bet_details_query = session.query(BetDetailModel).join(BetModel).filter( bet_details_query = session.query(BetDetailModel).join(BetModel).filter(
BetModel.bet_datetime >= start_datetime, BetModel.bet_datetime >= start_datetime,
BetModel.bet_datetime <= end_datetime BetModel.bet_datetime <= end_datetime,
BetDetailModel.result != 'cancelled'
) )
match_stats = {} match_stats = {}
......
...@@ -518,7 +518,8 @@ function updateBetsTable(data, container) { ...@@ -518,7 +518,8 @@ function updateBetsTable(data, container) {
<th><i class="fas fa-clock me-1"></i>Date & Time</th> <th><i class="fas fa-clock me-1"></i>Date & Time</th>
<th><i class="fas fa-list-ol me-1"></i>Details</th> <th><i class="fas fa-list-ol me-1"></i>Details</th>
<th><i class="fas fa-hashtag me-1"></i>Match</th> <th><i class="fas fa-hashtag me-1"></i>Match</th>
<th><i class="fas fa-dollar-sign me-1"></i>Total Amount</th> <th><i class="fas fa-dollar-sign me-1"></i>Payin</th>
<th><i class="fas fa-trophy me-1"></i>Payout</th>
<th><i class="fas fa-chart-line me-1"></i>Status</th> <th><i class="fas fa-chart-line me-1"></i>Status</th>
<th><i class="fas fa-money-bill me-1"></i>Payment</th> <th><i class="fas fa-money-bill me-1"></i>Payment</th>
<th><i class="fas fa-cogs me-1"></i>Actions</th> <th><i class="fas fa-cogs me-1"></i>Actions</th>
...@@ -561,6 +562,15 @@ function updateBetsTable(data, container) { ...@@ -561,6 +562,15 @@ function updateBetsTable(data, container) {
'<span class="badge bg-success"><i class="fas fa-check me-1"></i>Paid</span>' : '<span class="badge bg-success"><i class="fas fa-check me-1"></i>Paid</span>' :
'<span class="badge bg-secondary"><i class="fas fa-clock me-1"></i>Unpaid</span>'; '<span class="badge bg-secondary"><i class="fas fa-clock me-1"></i>Unpaid</span>';
// Calculate payout for winning bets
let payoutAmount = 0.0;
if (overallStatus === 'won' && bet.details) {
payoutAmount = bet.details
.filter(detail => detail.result === 'win')
.reduce((sum, detail) => sum + parseFloat(detail.win_amount || 0), 0);
}
const payoutDisplay = payoutAmount > 0 ? formatCurrency(payoutAmount.toFixed(2)) : '-';
tableHTML += ` tableHTML += `
<tr> <tr>
<td><strong>${bet.uuid.substring(0, 8)}...</strong></td> <td><strong>${bet.uuid.substring(0, 8)}...</strong></td>
...@@ -569,6 +579,7 @@ function updateBetsTable(data, container) { ...@@ -569,6 +579,7 @@ function updateBetsTable(data, container) {
<td>${bet.details ? bet.details.length : 0} selections</td> <td>${bet.details ? bet.details.length : 0} selections</td>
<td>${matchNumbers.length > 0 ? matchNumbers.join(', ') : 'N/A'}</td> <td>${matchNumbers.length > 0 ? matchNumbers.join(', ') : 'N/A'}</td>
<td><strong class="currency-amount" data-amount="${totalAmount}">${formatCurrency(totalAmount)}</strong></td> <td><strong class="currency-amount" data-amount="${totalAmount}">${formatCurrency(totalAmount)}</strong></td>
<td><strong class="currency-amount" data-amount="${payoutAmount}">${payoutDisplay}</strong></td>
<td>${statusBadge}</td> <td>${statusBadge}</td>
<td>${paidBadge}</td> <td>${paidBadge}</td>
<td> <td>
......
...@@ -518,7 +518,8 @@ function updateBetsTable(data, container) { ...@@ -518,7 +518,8 @@ function updateBetsTable(data, container) {
<th><i class="fas fa-clock me-1"></i>Date & Time</th> <th><i class="fas fa-clock me-1"></i>Date & Time</th>
<th><i class="fas fa-list-ol me-1"></i>Details</th> <th><i class="fas fa-list-ol me-1"></i>Details</th>
<th><i class="fas fa-hashtag me-1"></i>Match</th> <th><i class="fas fa-hashtag me-1"></i>Match</th>
<th><i class="fas fa-dollar-sign me-1"></i>Total Amount</th> <th><i class="fas fa-dollar-sign me-1"></i>Payin</th>
<th><i class="fas fa-trophy me-1"></i>Payout</th>
<th><i class="fas fa-chart-line me-1"></i>Status</th> <th><i class="fas fa-chart-line me-1"></i>Status</th>
<th><i class="fas fa-money-bill me-1"></i>Payment</th> <th><i class="fas fa-money-bill me-1"></i>Payment</th>
<th><i class="fas fa-cogs me-1"></i>Actions</th> <th><i class="fas fa-cogs me-1"></i>Actions</th>
...@@ -561,6 +562,15 @@ function updateBetsTable(data, container) { ...@@ -561,6 +562,15 @@ function updateBetsTable(data, container) {
'<span class="badge bg-success"><i class="fas fa-check me-1"></i>Paid</span>' : '<span class="badge bg-success"><i class="fas fa-check me-1"></i>Paid</span>' :
'<span class="badge bg-secondary"><i class="fas fa-clock me-1"></i>Unpaid</span>'; '<span class="badge bg-secondary"><i class="fas fa-clock me-1"></i>Unpaid</span>';
// Calculate payout for winning bets
let payoutAmount = 0.0;
if (overallStatus === 'won' && bet.details) {
payoutAmount = bet.details
.filter(detail => detail.result === 'win')
.reduce((sum, detail) => sum + parseFloat(detail.win_amount || 0), 0);
}
const payoutDisplay = payoutAmount > 0 ? formatCurrency(payoutAmount.toFixed(2)) : '-';
tableHTML += ` tableHTML += `
<tr> <tr>
<td><strong>${bet.uuid.substring(0, 8)}...</strong></td> <td><strong>${bet.uuid.substring(0, 8)}...</strong></td>
...@@ -569,6 +579,7 @@ function updateBetsTable(data, container) { ...@@ -569,6 +579,7 @@ function updateBetsTable(data, container) {
<td>${bet.details ? bet.details.length : 0} selections</td> <td>${bet.details ? bet.details.length : 0} selections</td>
<td>${matchNumbers.length > 0 ? matchNumbers.join(', ') : 'N/A'}</td> <td>${matchNumbers.length > 0 ? matchNumbers.join(', ') : 'N/A'}</td>
<td><strong class="currency-amount" data-amount="${totalAmount}">${formatCurrency(totalAmount)}</strong></td> <td><strong class="currency-amount" data-amount="${totalAmount}">${formatCurrency(totalAmount)}</strong></td>
<td><strong class="currency-amount" data-amount="${payoutAmount}">${payoutDisplay}</strong></td>
<td>${statusBadge}</td> <td>${statusBadge}</td>
<td>${paidBadge}</td> <td>${paidBadge}</td>
<td> <td>
......
This diff is collapsed.
pyqt6_amd64-2026-01-05T23:47:39Z.build
\ No newline at end of file
#!/usr/bin/env python3
"""
Test script to verify bet cancellation logic
"""
import sqlite3
import json
from datetime import datetime
def check_database():
"""Check what's in the database"""
from mbetterclient.config.settings import get_user_data_dir
db_path = get_user_data_dir() / "mbetterclient.db"
conn = sqlite3.connect(str(db_path))
cursor = conn.cursor()
# Check table names
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = cursor.fetchall()
print(f"Tables in database: {[t[0] for t in tables]}")
# Check bets
try:
cursor.execute("SELECT uuid, fixture_id, paid FROM bets LIMIT 5")
bets = cursor.fetchall()
print(f"\nFound {len(bets)} bets:")
for bet in bets:
print(f" UUID: {bet[0]}, Fixture: {bet[1]}, Paid: {bet[2]}")
# Check bet details for first bet
if bets:
bet_uuid = bets[0][0]
cursor.execute("SELECT id, bet_id, match_id, outcome, amount, result FROM bets_details WHERE bet_id = ?", (bet_uuid,))
details = cursor.fetchall()
print(f"\nBet details for {bet_uuid}:")
for detail in details:
print(f" ID: {detail[0]}, Match: {detail[2]}, Outcome: {detail[3]}, Amount: {detail[4]}, Result: {detail[5]}")
# Check match statuses for this bet
match_ids = [detail[2] for detail in details]
if match_ids:
placeholders = ','.join('?' * len(match_ids))
cursor.execute(f"SELECT id, status FROM matches WHERE id IN ({placeholders})", match_ids)
match_statuses = cursor.fetchall()
print(f"\nMatch statuses for bet {bet_uuid}:")
for match_id, status in match_statuses:
print(f" Match {match_id}: {status}")
except sqlite3.OperationalError as e:
print(f"\nError querying bets table: {e}")
# Check matches
try:
cursor.execute("SELECT id, match_number, status, fixture_id FROM matches ORDER BY id LIMIT 10")
matches = cursor.fetchall()
print(f"\nFound {len(matches)} matches (showing first 10):")
for match in matches:
print(f" ID: {match[0]}, Number: {match[1]}, Status: {match[2]}, Fixture: {match[3]}")
# Count matches by status
cursor.execute("SELECT status, COUNT(*) FROM matches GROUP BY status ORDER BY COUNT(*) DESC")
status_counts = cursor.fetchall()
print(f"\nMatch status counts:")
for status, count in status_counts:
print(f" {status}: {count}")
# Check total matches
cursor.execute("SELECT COUNT(*) FROM matches")
total_matches = cursor.fetchone()[0]
print(f"\nTotal matches in database: {total_matches}")
except sqlite3.OperationalError as e:
print(f"\nError querying matches table: {e}")
conn.close()
if __name__ == "__main__":
check_database()
\ No newline at end of file
#!/usr/bin/env python3
"""
Test script to verify the bet cancellation logic directly
"""
from mbetterclient.database.models import BetModel, BetDetailModel, MatchModel
from mbetterclient.database.manager import DatabaseManager
from mbetterclient.config.settings import get_user_data_dir
def test_cancel_logic():
"""Test the cancel logic directly"""
# Use the default database path
db_path = get_user_data_dir() / "mbetterclient.db"
db_manager = DatabaseManager(str(db_path))
if not db_manager.initialize():
print("Failed to initialize database")
return False
session = db_manager.get_session()
try:
# Get all bets and check their matches
bets = session.query(BetModel).all()
print(f"Found {len(bets)} bets total")
for bet in bets:
print(f"\nTesting cancellation for bet: {bet.uuid}")
# Get bet details
bet_details = session.query(BetDetailModel).filter_by(bet_id=bet.uuid).all()
print(f"Bet has {len(bet_details)} details")
# Check if any match has already started
match_ids = [detail.match_id for detail in bet_details]
matches = session.query(MatchModel).filter(MatchModel.id.in_(match_ids)).all()
print("Match statuses:")
for match in matches:
print(f" Match {match.id}: {match.status}")
# Check the logic
blocked = any(match.status in ['ingame', 'done'] for match in matches)
if blocked:
print("❌ Cancellation should be BLOCKED - match has already started")
else:
print("✅ Cancellation should be ALLOWED - no matches have started yet")
return True # Found one that should be allowed
print("\nAll existing bets are on matches that have already started.")
return True
# Get bet details
bet_details = session.query(BetDetailModel).filter_by(bet_id=bet.uuid).all()
print(f"Bet has {len(bet_details)} details")
# Check if any match has already started
match_ids = [detail.match_id for detail in bet_details]
matches = session.query(MatchModel).filter(MatchModel.id.in_(match_ids)).all()
print("Match statuses:")
for match in matches:
print(f" Match {match.id}: {match.status}")
# Check the logic
if any(match.status in ['ingame', 'done'] for match in matches):
print("❌ Cancellation should be BLOCKED - match has already started")
return True # Test passed - correctly blocked
else:
print("✅ Cancellation should be ALLOWED - no matches have started yet")
return True # Test passed - correctly allowed
except Exception as e:
print(f"Error: {e}")
return False
finally:
session.close()
db_manager.close()
if __name__ == "__main__":
print("Testing bet cancellation logic...")
success = test_cancel_logic()
if success:
print("\n✅ Logic test completed successfully!")
else:
print("\n❌ Logic test failed.")
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment