Fix barcode bet verification

parent 9ed29d92
......@@ -2548,6 +2548,73 @@ class Migration_032_FixExtractionAssociationDefaults(DatabaseMigration):
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
......@@ -2584,6 +2651,7 @@ MIGRATIONS: List[DatabaseMigration] = [
Migration_030_AddZipValidationStatus(),
Migration_031_AddWinningOutcomesFields(),
Migration_032_FixExtractionAssociationDefaults(),
Migration_033_AddBarcodeFieldsToBets(),
]
......
......@@ -678,15 +678,22 @@ class BetModel(BaseModel):
Index('ix_bets_uuid', 'uuid'),
Index('ix_bets_fixture_id', 'fixture_id'),
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('barcode_data', 'barcode_standard', name='uq_bets_barcode'),
)
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')
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_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
bet_details = relationship('BetDetailModel', back_populates='bet', cascade='all, delete-orphan')
......
......@@ -176,11 +176,19 @@ def bet_details(bet_id):
except (json.JSONDecodeError, TypeError):
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 = {
'id': detail.id,
'match_id': detail.match_id,
'outcome': detail.outcome,
'amount': float(detail.amount),
'odds': float(odds),
'potential_winning': float(detail.amount) * float(odds),
'result': detail.result,
'match': {
'match_number': match.match_number if match else 'Unknown',
......@@ -625,11 +633,19 @@ def cashier_bet_details(bet_id):
except (json.JSONDecodeError, TypeError):
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 = {
'id': detail.id,
'match_id': detail.match_id,
'outcome': detail.outcome,
'amount': float(detail.amount),
'odds': float(odds),
'potential_winning': float(detail.amount) * float(odds),
'result': detail.result,
'match': {
'match_number': match.match_number if match else 'Unknown',
......@@ -4140,6 +4156,33 @@ def create_cashier_bet():
)
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()
logger.info(f"Created bet {bet_uuid} with {len(bet_details)} details")
......@@ -4449,7 +4492,7 @@ def verify_bet_details(bet_id):
"""Get bet details for verification - no authentication required"""
try:
from ..database.models import BetModel, BetDetailModel, MatchModel
bet_uuid = str(bet_id)
session = api_bp.db_manager.get_session()
try:
......@@ -4464,12 +4507,84 @@ def verify_bet_details(bet_id):
# Get bet details with match information
bet_details = session.query(BetDetailModel).filter_by(bet_id=bet_uuid).all()
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
for detail in bet_details:
detail_data = detail.to_dict()
total_amount += float(detail.amount)
# Get match information
match = session.query(MatchModel).filter_by(id=detail.match_id).first()
if match:
......@@ -4498,7 +4613,7 @@ def verify_bet_details(bet_id):
'cancelled': 0,
'winnings': 0.0
}
overall_status = 'pending'
for detail in bet_details:
if detail.result == 'pending':
......@@ -4510,7 +4625,7 @@ def verify_bet_details(bet_id):
results['lost'] += 1
elif detail.result == 'cancelled':
results['cancelled'] += 1
# Determine overall status
if results['pending'] == 0:
if results['won'] > 0 and results['lost'] == 0:
......@@ -4519,7 +4634,7 @@ def verify_bet_details(bet_id):
overall_status = 'lost'
elif results['cancelled'] > 0:
overall_status = 'cancelled'
bet_data['overall_status'] = overall_status
bet_data['results'] = results
......
......@@ -501,14 +501,15 @@ function handleBarcodeInput(event) {
function processBarcodeInput() {
const input = document.getElementById('barcode-input');
const barcodeData = input.value.trim();
if (!barcodeData) {
showScannerStatus('Please enter a barcode value', 'warning');
return;
}
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) {
......@@ -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) {
const modalContent = document.getElementById('bet-details-content');
......
......@@ -482,14 +482,15 @@
function processBarcodeInput() {
const input = document.getElementById('barcode-input');
const barcodeData = input.value.trim();
if (!barcodeData) {
showScannerStatus('Please enter a barcode value', 'warning');
return;
}
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) {
......@@ -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) {
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