Rename 'Available Outcomes' to 'Available Bets' in extraction page

- Add AvailableBetModel with is_active and sort_order fields
- Create Migration_023_AddAvailableBetsTable with default betting options
- Add API endpoints for CRUD operations on available bets
- Update extraction page UI with add/delete functionality
- Update JavaScript to load from available_bets table instead of hardcoded outcomes
- Add modal dialog for adding new bet options
- Add delete buttons to each bet item in the UI
parent 7f1c1602
......@@ -10,19 +10,21 @@ from .models import (
ConfigurationModel,
ApiTokenModel,
LogEntryModel,
TemplateModel
TemplateModel,
AvailableBetModel
)
from .migrations import DatabaseMigration, run_migrations
__all__ = [
'DatabaseManager',
'BaseModel',
'DatabaseVersion',
'DatabaseVersion',
'UserModel',
'ConfigurationModel',
'ApiTokenModel',
'LogEntryModel',
'TemplateModel',
'AvailableBetModel',
'DatabaseMigration',
'run_migrations'
]
\ No newline at end of file
......@@ -1722,6 +1722,91 @@ class Migration_022_AddQRCodeConfiguration(DatabaseMigration):
logger.error(f"Failed to remove QR code configuration: {e}")
return False
class Migration_023_AddAvailableBetsTable(DatabaseMigration):
"""Add available_bets table for managing betting options in extraction page"""
def __init__(self):
super().__init__("023", "Add available_bets table for managing betting options")
def up(self, db_manager) -> bool:
"""Create available_bets table with default betting options"""
try:
with db_manager.engine.connect() as conn:
# Create available_bets table
conn.execute(text("""
CREATE TABLE IF NOT EXISTS available_bets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
bet_name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(255),
is_active BOOLEAN DEFAULT TRUE NOT NULL,
sort_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
"""))
# Create indexes for available_bets table
indexes = [
"CREATE INDEX IF NOT EXISTS ix_available_bets_bet_name ON available_bets(bet_name)",
"CREATE INDEX IF NOT EXISTS ix_available_bets_is_active ON available_bets(is_active)",
"CREATE INDEX IF NOT EXISTS ix_available_bets_sort_order ON available_bets(sort_order)",
]
for index_sql in indexes:
conn.execute(text(index_sql))
# Insert default betting options
default_bets = [
('DRAW', 'Draw outcome', 1),
('DKO', 'Double Knockout', 2),
('X1', 'Draw No Bet - Fighter 1', 3),
('X2', 'Draw No Bet - Fighter 2', 4),
('12', '1X2 betting (1, X, 2)', 5),
('WIN1', 'Fighter 1 wins', 6),
('WIN2', 'Fighter 2 wins', 7),
('RET1', 'Fighter 1 retires', 8),
('RET2', 'Fighter 2 retires', 9),
('PTS1', 'Fighter 1 wins by points', 10),
('PTS2', 'Fighter 2 wins by points', 11),
('OVER', 'Over time limit', 12),
('UNDER', 'Under time limit', 13),
('KO1', 'Fighter 1 wins by KO', 14),
('KO2', 'Fighter 2 wins by KO', 15),
]
for bet_name, description, sort_order in default_bets:
conn.execute(text("""
INSERT OR IGNORE INTO available_bets
(bet_name, description, is_active, sort_order, created_at, updated_at)
VALUES (:bet_name, :description, 1, :sort_order, datetime('now'), datetime('now'))
"""), {
'bet_name': bet_name,
'description': description,
'sort_order': sort_order
})
conn.commit()
logger.info("Available bets table created with default betting options")
return True
except Exception as e:
logger.error(f"Failed to create available_bets table: {e}")
return False
def down(self, db_manager) -> bool:
"""Drop available_bets table"""
try:
with db_manager.engine.connect() as conn:
conn.execute(text("DROP TABLE IF EXISTS available_bets"))
conn.commit()
logger.info("Available bets table dropped")
return True
except Exception as e:
logger.error(f"Failed to drop available_bets table: {e}")
return False
# Registry of all migrations in order
MIGRATIONS: List[DatabaseMigration] = [
Migration_001_InitialSchema(),
......@@ -1746,6 +1831,7 @@ MIGRATIONS: List[DatabaseMigration] = [
Migration_020_FixBetDetailsForeignKey(),
Migration_021_AddBarcodeConfiguration(),
Migration_022_AddQRCodeConfiguration(),
Migration_023_AddAvailableBetsTable(),
]
......
......@@ -762,3 +762,22 @@ class BetDetailModel(BaseModel):
def __repr__(self):
return f'<BetDetail {self.outcome}={self.amount} ({self.result}) for Match {self.match_id}>'
class AvailableBetModel(BaseModel):
"""Available betting options for extraction system"""
__tablename__ = 'available_bets'
__table_args__ = (
Index('ix_available_bets_bet_name', 'bet_name'),
Index('ix_available_bets_is_active', 'is_active'),
Index('ix_available_bets_sort_order', 'sort_order'),
UniqueConstraint('bet_name', name='uq_available_bets_bet_name'),
)
bet_name = Column(String(50), nullable=False, unique=True, comment='Bet option name (e.g., WIN1, DRAW, X)')
description = Column(String(255), comment='Description of the bet option')
is_active = Column(Boolean, default=True, nullable=False, comment='Whether this bet option is active')
sort_order = Column(Integer, default=0, comment='Sort order for display')
def __repr__(self):
return f'<AvailableBet {self.bet_name}: {self.description}>'
......@@ -3416,6 +3416,121 @@ def save_results_config():
return jsonify({"error": str(e)}), 500
# Available Bets API routes
@api_bp.route('/extraction/available-bets')
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def get_available_bets():
"""Get all available bets for extraction"""
try:
from ..database.models import AvailableBetModel
session = api_bp.db_manager.get_session()
try:
# Get all available bets
bets = session.query(AvailableBetModel).order_by(AvailableBetModel.bet_name.asc()).all()
bets_data = [bet.to_dict() for bet in bets]
return jsonify({
"success": True,
"bets": bets_data,
"total": len(bets_data)
})
finally:
session.close()
except Exception as e:
logger.error(f"API get available bets error: {e}")
return jsonify({"error": str(e)}), 500
@api_bp.route('/extraction/available-bets/add', methods=['POST'])
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def add_available_bet():
"""Add a new available bet"""
try:
from ..database.models import AvailableBetModel
data = request.get_json() or {}
bet_name = data.get('bet_name', '').strip().upper()
description = data.get('description', '').strip()
if not bet_name:
return jsonify({"error": "bet_name is required"}), 400
if len(bet_name) > 50:
return jsonify({"error": "bet_name must be 50 characters or less"}), 400
session = api_bp.db_manager.get_session()
try:
# Check if bet already exists
existing_bet = session.query(AvailableBetModel).filter_by(bet_name=bet_name).first()
if existing_bet:
return jsonify({"error": f"Bet '{bet_name}' already exists"}), 400
# Create new bet
new_bet = AvailableBetModel(
bet_name=bet_name,
description=description or f"Bet option: {bet_name}"
)
session.add(new_bet)
session.commit()
logger.info(f"Added available bet: {bet_name}")
return jsonify({
"success": True,
"message": f"Bet '{bet_name}' added successfully",
"bet": new_bet.to_dict()
})
finally:
session.close()
except Exception as e:
logger.error(f"API add available bet error: {e}")
return jsonify({"error": str(e)}), 500
@api_bp.route('/extraction/available-bets/delete', methods=['POST'])
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def delete_available_bet():
"""Delete an available bet"""
try:
from ..database.models import AvailableBetModel
data = request.get_json() or {}
bet_name = data.get('bet_name', '').strip().upper()
if not bet_name:
return jsonify({"error": "bet_name is required"}), 400
session = api_bp.db_manager.get_session()
try:
# Find the bet
bet = session.query(AvailableBetModel).filter_by(bet_name=bet_name).first()
if not bet:
return jsonify({"error": f"Bet '{bet_name}' not found"}), 404
# Delete the bet
session.delete(bet)
session.commit()
logger.info(f"Deleted available bet: {bet_name}")
return jsonify({
"success": True,
"message": f"Bet '{bet_name}' deleted successfully"
})
finally:
session.close()
except Exception as e:
logger.error(f"API delete available bet error: {e}")
return jsonify({"error": str(e)}), 500
# Redistribution CAP API routes (admin-only)
@api_bp.route('/extraction/redistribution-cap')
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
......
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