Fix barcode bet verification

parent 9ed29d92
...@@ -2548,6 +2548,73 @@ class Migration_032_FixExtractionAssociationDefaults(DatabaseMigration): ...@@ -2548,6 +2548,73 @@ class Migration_032_FixExtractionAssociationDefaults(DatabaseMigration):
return False return False
class Migration_033_AddBarcodeFieldsToBets(DatabaseMigration):
"""Add barcode_standard and barcode_data fields to bets table for barcode verification"""
def __init__(self):
super().__init__("033", "Add barcode_standard and barcode_data fields to bets table")
def up(self, db_manager) -> bool:
"""Add barcode fields to bets table"""
try:
with db_manager.engine.connect() as conn:
# Check if columns already exist
result = conn.execute(text("PRAGMA table_info(bets)"))
columns = [row[1] for row in result.fetchall()]
if 'barcode_standard' not in columns:
# Add barcode_standard column
conn.execute(text("""
ALTER TABLE bets
ADD COLUMN barcode_standard VARCHAR(50)
"""))
logger.info("barcode_standard column added to bets table")
else:
logger.info("barcode_standard column already exists in bets table")
if 'barcode_data' not in columns:
# Add barcode_data column
conn.execute(text("""
ALTER TABLE bets
ADD COLUMN barcode_data VARCHAR(255)
"""))
logger.info("barcode_data column added to bets table")
else:
logger.info("barcode_data column already exists in bets table")
# Add indexes for barcode fields
conn.execute(text("""
CREATE INDEX IF NOT EXISTS ix_bets_barcode_data ON bets(barcode_data)
"""))
conn.execute(text("""
CREATE INDEX IF NOT EXISTS ix_bets_barcode_standard ON bets(barcode_standard)
"""))
# Add unique constraint for barcode data per standard
try:
conn.execute(text("""
CREATE UNIQUE INDEX IF NOT EXISTS uq_bets_barcode ON bets(barcode_data, barcode_standard)
"""))
except Exception as e:
logger.warning(f"Could not create unique constraint on barcode fields: {e}")
conn.commit()
logger.info("Barcode fields added to bets table successfully")
return True
except Exception as e:
logger.error(f"Failed to add barcode fields to bets table: {e}")
return False
def down(self, db_manager) -> bool:
"""Remove barcode fields - SQLite doesn't support DROP COLUMN easily"""
logger.warning("SQLite doesn't support DROP COLUMN - barcode fields will remain")
return True
# Registry of all migrations in order # Registry of all migrations in order
# Registry of all migrations in order # Registry of all migrations in order
...@@ -2584,6 +2651,7 @@ MIGRATIONS: List[DatabaseMigration] = [ ...@@ -2584,6 +2651,7 @@ MIGRATIONS: List[DatabaseMigration] = [
Migration_030_AddZipValidationStatus(), Migration_030_AddZipValidationStatus(),
Migration_031_AddWinningOutcomesFields(), Migration_031_AddWinningOutcomesFields(),
Migration_032_FixExtractionAssociationDefaults(), Migration_032_FixExtractionAssociationDefaults(),
Migration_033_AddBarcodeFieldsToBets(),
] ]
......
...@@ -678,15 +678,22 @@ class BetModel(BaseModel): ...@@ -678,15 +678,22 @@ class BetModel(BaseModel):
Index('ix_bets_uuid', 'uuid'), Index('ix_bets_uuid', 'uuid'),
Index('ix_bets_fixture_id', 'fixture_id'), Index('ix_bets_fixture_id', 'fixture_id'),
Index('ix_bets_created_at', 'created_at'), Index('ix_bets_created_at', 'created_at'),
Index('ix_bets_barcode_data', 'barcode_data'),
Index('ix_bets_barcode_standard', 'barcode_standard'),
UniqueConstraint('uuid', name='uq_bets_uuid'), UniqueConstraint('uuid', name='uq_bets_uuid'),
UniqueConstraint('barcode_data', 'barcode_standard', name='uq_bets_barcode'),
) )
uuid = Column(String(1024), nullable=False, unique=True, comment='Unique identifier for the bet') uuid = Column(String(1024), nullable=False, unique=True, comment='Unique identifier for the bet')
fixture_id = Column(String(255), nullable=False, comment='Reference to fixture_id from matches table') fixture_id = Column(String(255), nullable=False, comment='Reference to fixture_id from matches table')
bet_datetime = Column(DateTime, default=datetime.utcnow, nullable=False, comment='Bet creation timestamp') bet_datetime = Column(DateTime, default=datetime.utcnow, nullable=False, comment='Bet creation timestamp')
paid = Column(Boolean, default=False, nullable=False, comment='Payment status (True if payment received)') paid = Column(Boolean, default=False, nullable=False, comment='Payment status (True if payment received)')
paid_out = Column(Boolean, default=False, nullable=False, comment='Payout status (True if winnings paid out)') paid_out = Column(Boolean, default=False, nullable=False, comment='Payout status (True if winnings paid out)')
# Barcode fields for verification
barcode_standard = Column(String(50), comment='Barcode standard used (ean13, code128, etc.)')
barcode_data = Column(String(255), comment='Barcode data for verification')
# Relationships # Relationships
bet_details = relationship('BetDetailModel', back_populates='bet', cascade='all, delete-orphan') bet_details = relationship('BetDetailModel', back_populates='bet', cascade='all, delete-orphan')
......
...@@ -176,11 +176,19 @@ def bet_details(bet_id): ...@@ -176,11 +176,19 @@ def bet_details(bet_id):
except (json.JSONDecodeError, TypeError): except (json.JSONDecodeError, TypeError):
winning_outcomes = [] winning_outcomes = []
# Get odds for this outcome
odds = 0.0
if match:
outcomes_dict = match.get_outcomes_dict()
odds = outcomes_dict.get(detail.outcome, 0.0)
detail_dict = { detail_dict = {
'id': detail.id, 'id': detail.id,
'match_id': detail.match_id, 'match_id': detail.match_id,
'outcome': detail.outcome, 'outcome': detail.outcome,
'amount': float(detail.amount), 'amount': float(detail.amount),
'odds': float(odds),
'potential_winning': float(detail.amount) * float(odds),
'result': detail.result, 'result': detail.result,
'match': { 'match': {
'match_number': match.match_number if match else 'Unknown', 'match_number': match.match_number if match else 'Unknown',
...@@ -625,11 +633,19 @@ def cashier_bet_details(bet_id): ...@@ -625,11 +633,19 @@ def cashier_bet_details(bet_id):
except (json.JSONDecodeError, TypeError): except (json.JSONDecodeError, TypeError):
winning_outcomes = [] winning_outcomes = []
# Get odds for this outcome
odds = 0.0
if match:
outcomes_dict = match.get_outcomes_dict()
odds = outcomes_dict.get(detail.outcome, 0.0)
detail_dict = { detail_dict = {
'id': detail.id, 'id': detail.id,
'match_id': detail.match_id, 'match_id': detail.match_id,
'outcome': detail.outcome, 'outcome': detail.outcome,
'amount': float(detail.amount), 'amount': float(detail.amount),
'odds': float(odds),
'potential_winning': float(detail.amount) * float(odds),
'result': detail.result, 'result': detail.result,
'match': { 'match': {
'match_number': match.match_number if match else 'Unknown', 'match_number': match.match_number if match else 'Unknown',
...@@ -4140,6 +4156,33 @@ def create_cashier_bet(): ...@@ -4140,6 +4156,33 @@ def create_cashier_bet():
) )
session.add(bet_detail) session.add(bet_detail)
# Generate and store barcode data if enabled
try:
from ..utils.barcode_utils import format_bet_id_for_barcode
# Get barcode configuration
if api_bp.db_manager:
barcode_enabled = api_bp.db_manager.get_config_value('barcode.enabled', False)
barcode_standard = api_bp.db_manager.get_config_value('barcode.standard', 'none')
if barcode_enabled and barcode_standard != 'none':
# Generate barcode data for the bet
barcode_data = format_bet_id_for_barcode(bet_uuid, barcode_standard)
# Update the bet with barcode information
new_bet.barcode_standard = barcode_standard
new_bet.barcode_data = barcode_data
session.commit()
logger.info(f"Generated barcode data for bet {bet_uuid}: {barcode_standard} -> {barcode_data}")
else:
logger.debug(f"Barcode generation disabled or not configured for bet {bet_uuid}")
else:
logger.warning("Database manager not available for barcode generation")
except Exception as barcode_e:
logger.error(f"Failed to generate barcode data for bet {bet_uuid}: {barcode_e}")
# Don't fail the bet creation if barcode generation fails
session.commit() session.commit()
logger.info(f"Created bet {bet_uuid} with {len(bet_details)} details") logger.info(f"Created bet {bet_uuid} with {len(bet_details)} details")
...@@ -4449,7 +4492,7 @@ def verify_bet_details(bet_id): ...@@ -4449,7 +4492,7 @@ def verify_bet_details(bet_id):
"""Get bet details for verification - no authentication required""" """Get bet details for verification - no authentication required"""
try: try:
from ..database.models import BetModel, BetDetailModel, MatchModel 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()
try: try:
...@@ -4464,12 +4507,84 @@ def verify_bet_details(bet_id): ...@@ -4464,12 +4507,84 @@ def verify_bet_details(bet_id):
# Get bet details with match information # Get bet details with match information
bet_details = session.query(BetDetailModel).filter_by(bet_id=bet_uuid).all() bet_details = session.query(BetDetailModel).filter_by(bet_id=bet_uuid).all()
details_data = [] details_data = []
for detail in bet_details:
detail_data = detail.to_dict()
# Get match information
match = session.query(MatchModel).filter_by(id=detail.match_id).first()
if match:
detail_data['match'] = {
'match_number': match.match_number,
'fighter1_township': match.fighter1_township,
'fighter2_township': match.fighter2_township,
'venue_kampala_township': match.venue_kampala_township,
'status': match.status,
'result': match.result
}
else:
detail_data['match'] = None
details_data.append(detail_data)
bet_data['details'] = details_data
bet_data['details_count'] = len(details_data)
# Calculate total amount
total_amount = sum(float(detail.amount) for detail in bet_details)
bet_data['total_amount'] = total_amount
return jsonify({
"success": True,
"bet": bet_data
})
finally:
session.close()
except Exception as e:
logger.error(f"API verify bet details error: {e}")
return jsonify({"error": str(e)}), 500
@api_bp.route('/verify-barcode')
def verify_barcode():
"""Get bet details for verification by barcode data - no authentication required"""
try:
from ..database.models import BetModel, BetDetailModel, MatchModel
# Get barcode data from query parameters
barcode_data = request.args.get('data', '').strip()
barcode_standard = request.args.get('standard', '').strip()
if not barcode_data:
return jsonify({"error": "Barcode data is required"}), 400
session = api_bp.db_manager.get_session()
try:
# Look up bet by barcode data and standard
query = session.query(BetModel).filter_by(barcode_data=barcode_data)
# If standard is provided, also filter by it
if barcode_standard:
query = query.filter_by(barcode_standard=barcode_standard)
bet = query.first()
if not bet:
return jsonify({"error": "Bet not found for this barcode"}), 404
bet_data = bet.to_dict()
bet_data['paid'] = bet.paid # Include paid status
# Get bet details with match information
bet_details = session.query(BetDetailModel).filter_by(bet_id=bet.uuid).all()
details_data = []
total_amount = 0.0 total_amount = 0.0
for detail in bet_details: for detail in bet_details:
detail_data = detail.to_dict() detail_data = detail.to_dict()
total_amount += float(detail.amount) total_amount += float(detail.amount)
# Get match information # Get match information
match = session.query(MatchModel).filter_by(id=detail.match_id).first() match = session.query(MatchModel).filter_by(id=detail.match_id).first()
if match: if match:
...@@ -4498,7 +4613,7 @@ def verify_bet_details(bet_id): ...@@ -4498,7 +4613,7 @@ def verify_bet_details(bet_id):
'cancelled': 0, 'cancelled': 0,
'winnings': 0.0 'winnings': 0.0
} }
overall_status = 'pending' overall_status = 'pending'
for detail in bet_details: for detail in bet_details:
if detail.result == 'pending': if detail.result == 'pending':
...@@ -4510,7 +4625,7 @@ def verify_bet_details(bet_id): ...@@ -4510,7 +4625,7 @@ def verify_bet_details(bet_id):
results['lost'] += 1 results['lost'] += 1
elif detail.result == 'cancelled': elif detail.result == 'cancelled':
results['cancelled'] += 1 results['cancelled'] += 1
# Determine overall status # Determine overall status
if results['pending'] == 0: if results['pending'] == 0:
if results['won'] > 0 and results['lost'] == 0: if results['won'] > 0 and results['lost'] == 0:
...@@ -4519,7 +4634,7 @@ def verify_bet_details(bet_id): ...@@ -4519,7 +4634,7 @@ def verify_bet_details(bet_id):
overall_status = 'lost' overall_status = 'lost'
elif results['cancelled'] > 0: elif results['cancelled'] > 0:
overall_status = 'cancelled' overall_status = 'cancelled'
bet_data['overall_status'] = overall_status bet_data['overall_status'] = overall_status
bet_data['results'] = results bet_data['results'] = results
......
...@@ -501,14 +501,15 @@ function handleBarcodeInput(event) { ...@@ -501,14 +501,15 @@ function handleBarcodeInput(event) {
function processBarcodeInput() { function processBarcodeInput() {
const input = document.getElementById('barcode-input'); const input = document.getElementById('barcode-input');
const barcodeData = input.value.trim(); const barcodeData = input.value.trim();
if (!barcodeData) { if (!barcodeData) {
showScannerStatus('Please enter a barcode value', 'warning'); showScannerStatus('Please enter a barcode value', 'warning');
return; return;
} }
console.log('Barcode input detected:', barcodeData); console.log('Barcode input detected:', barcodeData);
handleCodeDetected(barcodeData, 'Barcode'); // For manual barcode input, we don't know the standard, so try to verify directly
verifyBarcode(barcodeData);
} }
function extractUuidFromBarcode(barcodeData) { function extractUuidFromBarcode(barcodeData) {
...@@ -550,6 +551,29 @@ function verifyBet(betUuid) { ...@@ -550,6 +551,29 @@ function verifyBet(betUuid) {
}); });
} }
function verifyBarcode(barcodeData, barcodeStandard = null) {
const params = new URLSearchParams({
data: barcodeData
});
if (barcodeStandard) {
params.append('standard', barcodeStandard);
}
fetch(`/api/verify-barcode?${params}`)
.then(response => response.json())
.then(data => {
if (data.success) {
displayBetDetails(data.bet);
} else {
showScannerStatus('Bet not found: ' + (data.error || 'Unknown error'), 'danger');
}
})
.catch(error => {
console.error('Error verifying barcode:', error);
showScannerStatus('Error verifying barcode: ' + error.message, 'danger');
});
}
function displayBetDetails(bet) { function displayBetDetails(bet) {
const modalContent = document.getElementById('bet-details-content'); const modalContent = document.getElementById('bet-details-content');
......
...@@ -482,14 +482,15 @@ ...@@ -482,14 +482,15 @@
function processBarcodeInput() { function processBarcodeInput() {
const input = document.getElementById('barcode-input'); const input = document.getElementById('barcode-input');
const barcodeData = input.value.trim(); const barcodeData = input.value.trim();
if (!barcodeData) { if (!barcodeData) {
showScannerStatus('Please enter a barcode value', 'warning'); showScannerStatus('Please enter a barcode value', 'warning');
return; return;
} }
console.log('Barcode input detected:', barcodeData); console.log('Barcode input detected:', barcodeData);
handleCodeDetected(barcodeData, 'Barcode'); // For manual barcode input, we don't know the standard, so try to verify directly
verifyBarcode(barcodeData);
} }
function extractUuidFromBarcode(barcodeData) { function extractUuidFromBarcode(barcodeData) {
...@@ -531,6 +532,29 @@ ...@@ -531,6 +532,29 @@
}); });
} }
function verifyBarcode(barcodeData, barcodeStandard = null) {
const params = new URLSearchParams({
data: barcodeData
});
if (barcodeStandard) {
params.append('standard', barcodeStandard);
}
fetch(`/api/verify-barcode?${params}`)
.then(response => response.json())
.then(data => {
if (data.success) {
displayBetDetails(data.bet);
} else {
showScannerStatus('Bet not found: ' + (data.error || 'Unknown error'), 'danger');
}
})
.catch(error => {
console.error('Error verifying barcode:', error);
showScannerStatus('Error verifying barcode. Please check your connection.', 'danger');
});
}
function displayBetDetails(bet) { function displayBetDetails(bet) {
const modalContent = document.getElementById('bet-details-content'); const modalContent = document.getElementById('bet-details-content');
......
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