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,7 +678,10 @@ class BetModel(BaseModel): ...@@ -678,7 +678,10 @@ 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')
...@@ -687,6 +690,10 @@ class BetModel(BaseModel): ...@@ -687,6 +690,10 @@ class BetModel(BaseModel):
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")
...@@ -4464,6 +4507,78 @@ def verify_bet_details(bet_id): ...@@ -4464,6 +4507,78 @@ 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:
......
...@@ -445,6 +445,8 @@ ...@@ -445,6 +445,8 @@
"venue": "{{ detail.match.venue_kampala_township if detail.match else 'Unknown' }}", "venue": "{{ detail.match.venue_kampala_township if detail.match else 'Unknown' }}",
"outcome": "{{ detail.outcome }}", "outcome": "{{ detail.outcome }}",
"amount": {{ detail.amount|round(2) }}, "amount": {{ detail.amount|round(2) }},
"odds": {{ detail.odds|round(2) }},
"potential_winning": {{ detail.potential_winning|round(2) }},
"result": "{{ detail.result }}" "result": "{{ detail.result }}"
}{% if not loop.last %},{% endif %} }{% if not loop.last %},{% endif %}
{% endfor %} {% endfor %}
...@@ -628,6 +630,10 @@ function generateReceiptHtml(betData) { ...@@ -628,6 +630,10 @@ function generateReceiptHtml(betData) {
<span>OUTCOME: ${detail.outcome}</span> <span>OUTCOME: ${detail.outcome}</span>
<span>${formatCurrency(parseFloat(detail.amount))}</span> <span>${formatCurrency(parseFloat(detail.amount))}</span>
</div> </div>
<div class="receipt-bet-line">
<span>ODDS: ${parseFloat(detail.odds).toFixed(2)}</span>
<span>POTENTIAL WIN: ${formatCurrency(parseFloat(detail.potential_winning))}</span>
</div>
<div class="receipt-status"> <div class="receipt-status">
STATUS: ${detail.result.toUpperCase()} STATUS: ${detail.result.toUpperCase()}
</div> </div>
...@@ -774,6 +780,111 @@ function generateVerificationCodes(betUuid) { ...@@ -774,6 +780,111 @@ function generateVerificationCodes(betUuid) {
} }
function printThermalReceipt() { function printThermalReceipt() {
// Mark bet as paid before printing
markBetAsPaidForPrinting(window.betData.uuid).then(() => {
const printContent = document.getElementById('thermal-receipt').innerHTML;
const printWindow = window.open('', '', 'height=600,width=400');
printWindow.document.write(`
<html>
<head>
<title>Betting Receipt</title>
<style>
@media print {
body { margin: 0; padding: 10px; font-family: 'Courier New', monospace; }
.thermal-receipt-content { width: 100%; }
.receipt-header { text-align: center; margin-bottom: 10px; }
.receipt-title { font-size: 18px; font-weight: bold; margin-bottom: 2px; }
.receipt-separator { text-align: center; margin: 8px 0; font-size: 10px; }
.receipt-info, .receipt-bets, .receipt-total, .receipt-footer { margin: 10px 0; }
.receipt-row, .receipt-bet-line, .receipt-total-line {
display: flex; justify-content: space-between; margin-bottom: 2px; font-size: 10px;
}
.receipt-bet-item { margin-bottom: 8px; }
.receipt-match { font-weight: bold; font-size: 12px; text-align: center; }
.receipt-match-details { font-size: 10px; text-align: center; margin-bottom: 2px; }
.receipt-venue { font-size: 9px; text-align: center; margin-bottom: 3px; }
.receipt-status { font-size: 9px; text-align: center; margin-top: 2px; }
.receipt-total { border-top: 1px solid #000; padding-top: 5px; font-weight: bold; }
.receipt-verification { text-align: center; margin: 10px 0; }
.receipt-qr, .receipt-barcode { margin: 5px 0; }
.qr-image { width: 80px; height: 80px; }
.qr-text, .barcode-text { font-size: 9px; margin-top: 3px; }
.barcode-img { max-width: 120px; height: auto; }
.receipt-footer { text-align: center; font-size: 9px; margin-top: 10px; border-top: 1px solid #000; padding-top: 5px; }
.receipt-timestamp { margin-top: 5px; font-size: 8px; }
}
body {
font-family: 'Courier New', monospace;
font-size: 11px;
line-height: 1.2;
color: #000;
background: #fff;
}
.thermal-receipt-content {
max-width: 300px;
margin: 0 auto;
padding: 10px;
}
.receipt-header { text-align: center; margin-bottom: 15px; }
.receipt-title { font-size: 20px; font-weight: bold; margin-bottom: 3px; letter-spacing: 2px; }
.receipt-separator {
text-align: center;
margin: 12px 0;
font-size: 11px;
letter-spacing: -1px;
}
.receipt-info, .receipt-bets, .receipt-total, .receipt-footer { margin: 15px 0; }
.receipt-row, .receipt-bet-line, .receipt-total-line {
display: flex;
justify-content: space-between;
margin-bottom: 3px;
font-size: 11px;
}
.receipt-bet-item { margin-bottom: 12px; }
.receipt-match { font-weight: bold; font-size: 13px; text-align: center; }
.receipt-match-details { font-size: 11px; text-align: center; margin-bottom: 3px; }
.receipt-venue { font-size: 10px; text-align: center; margin-bottom: 4px; color: #666; }
.receipt-status { font-size: 10px; text-align: center; margin-top: 3px; font-weight: bold; }
.receipt-total {
border-top: 2px solid #000;
padding-top: 8px;
font-weight: bold;
font-size: 14px;
}
.receipt-verification { text-align: center; margin: 15px 0; }
.receipt-qr, .receipt-barcode { margin: 8px 0; }
.qr-image { width: 100px; height: 100px; border: 1px solid #ccc; }
.qr-text, .barcode-text { font-size: 10px; margin-top: 5px; }
.barcode-img { max-width: 150px; height: auto; border: 1px solid #ccc; }
.receipt-footer {
text-align: center;
font-size: 10px;
margin-top: 15px;
border-top: 1px solid #000;
padding-top: 8px;
}
.receipt-timestamp { margin-top: 8px; font-size: 9px; color: #666; }
</style>
</head>
<body>
${printContent}
</body>
</html>
`);
printWindow.document.close();
printWindow.focus();
// Wait for images to load then print
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 500);
}).catch(error => {
console.error('Failed to mark bet as paid:', error);
// Still print even if marking as paid fails
const printContent = document.getElementById('thermal-receipt').innerHTML; const printContent = document.getElementById('thermal-receipt').innerHTML;
const printWindow = window.open('', '', 'height=600,width=400'); const printWindow = window.open('', '', 'height=600,width=400');
...@@ -874,6 +985,7 @@ function printThermalReceipt() { ...@@ -874,6 +985,7 @@ function printThermalReceipt() {
printWindow.print(); printWindow.print();
printWindow.close(); printWindow.close();
}, 500); }, 500);
});
} }
function deleteBetDetail(detailId) { function deleteBetDetail(detailId) {
...@@ -992,10 +1104,35 @@ function markBetAsPaid(betUuid) { ...@@ -992,10 +1104,35 @@ function markBetAsPaid(betUuid) {
} }
} }
function markBetAsPaidForPrinting(betUuid) {
return fetch(`/api/bets/${betUuid}/mark-paid`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Bet marked as paid for printing');
} else {
throw new Error(data.error || 'Failed to mark bet as paid');
}
});
}
function directPrintBet(betId) { function directPrintBet(betId) {
// Mark bet as paid before printing
markBetAsPaidForPrinting(betId).then(() => {
// Use the global bet data for direct printing // Use the global bet data for direct printing
const receiptHtml = generateReceiptHtml(window.betData); const receiptHtml = generateReceiptHtml(window.betData);
printDirectly(receiptHtml); printDirectly(receiptHtml);
}).catch(error => {
console.error('Failed to mark bet as paid:', error);
// Still print even if marking as paid fails
const receiptHtml = generateReceiptHtml(window.betData);
printDirectly(receiptHtml);
});
} }
function printDirectly(printContent) { function printDirectly(printContent) {
......
...@@ -445,6 +445,8 @@ ...@@ -445,6 +445,8 @@
"venue": "{{ detail.match.venue_kampala_township if detail.match else 'Unknown' }}", "venue": "{{ detail.match.venue_kampala_township if detail.match else 'Unknown' }}",
"outcome": "{{ detail.outcome }}", "outcome": "{{ detail.outcome }}",
"amount": {{ detail.amount|round(2) }}, "amount": {{ detail.amount|round(2) }},
"odds": {{ detail.odds|round(2) }},
"potential_winning": {{ detail.potential_winning|round(2) }},
"result": "{{ detail.result }}" "result": "{{ detail.result }}"
}{% if not loop.last %},{% endif %} }{% if not loop.last %},{% endif %}
{% endfor %} {% endfor %}
...@@ -685,6 +687,10 @@ function generateReceiptHtml(betData) { ...@@ -685,6 +687,10 @@ function generateReceiptHtml(betData) {
<span>OUTCOME: ${detail.outcome}</span> <span>OUTCOME: ${detail.outcome}</span>
<span>${formatCurrency(parseFloat(detail.amount))}</span> <span>${formatCurrency(parseFloat(detail.amount))}</span>
</div> </div>
<div class="receipt-bet-line">
<span>ODDS: ${parseFloat(detail.odds).toFixed(2)}</span>
<span>POTENTIAL WIN: ${formatCurrency(parseFloat(detail.potential_winning))}</span>
</div>
<div class="receipt-status"> <div class="receipt-status">
STATUS: ${detail.result.toUpperCase()} STATUS: ${detail.result.toUpperCase()}
</div> </div>
...@@ -821,6 +827,111 @@ function generateVerificationCodes(betUuid) { ...@@ -821,6 +827,111 @@ function generateVerificationCodes(betUuid) {
} }
function printThermalReceipt() { function printThermalReceipt() {
// Mark bet as paid before printing
markBetAsPaidForPrinting(window.betData.uuid).then(() => {
const printContent = document.getElementById('thermal-receipt').innerHTML;
const printWindow = window.open('', '', 'height=600,width=400');
printWindow.document.write(`
<html>
<head>
<title>Betting Receipt</title>
<style>
@media print {
body { margin: 0; padding: 10px; font-family: 'Courier New', monospace; }
.thermal-receipt-content { width: 100%; }
.receipt-header { text-align: center; margin-bottom: 10px; }
.receipt-title { font-size: 18px; font-weight: bold; margin-bottom: 2px; }
.receipt-separator { text-align: center; margin: 8px 0; font-size: 10px; }
.receipt-info, .receipt-bets, .receipt-total, .receipt-footer { margin: 10px 0; }
.receipt-row, .receipt-bet-line, .receipt-total-line {
display: flex; justify-content: space-between; margin-bottom: 2px; font-size: 10px;
}
.receipt-bet-item { margin-bottom: 8px; }
.receipt-match { font-weight: bold; font-size: 12px; text-align: center; }
.receipt-match-details { font-size: 10px; text-align: center; margin-bottom: 2px; }
.receipt-venue { font-size: 9px; text-align: center; margin-bottom: 3px; }
.receipt-status { font-size: 9px; text-align: center; margin-top: 2px; }
.receipt-total { border-top: 1px solid #000; padding-top: 5px; font-weight: bold; }
.receipt-verification { text-align: center; margin: 10px 0; }
.receipt-qr, .receipt-barcode { margin: 5px 0; text-align: center; }
.qr-image { width: 80px; height: 80px; }
.qr-text, .barcode-text { font-size: 9px; margin-top: 3px; }
.barcode-img { width: auto; height: auto; max-width: 150px; }
.receipt-footer { text-align: center; font-size: 9px; margin-top: 10px; border-top: 1px solid #000; padding-top: 5px; }
.receipt-timestamp { margin-top: 5px; font-size: 8px; }
}
body {
font-family: 'Courier New', monospace;
font-size: 11px;
line-height: 1.2;
color: #000;
background: #fff;
}
.thermal-receipt-content {
max-width: 300px;
margin: 0 auto;
padding: 10px;
}
.receipt-header { text-align: center; margin-bottom: 15px; }
.receipt-title { font-size: 20px; font-weight: bold; margin-bottom: 3px; letter-spacing: 2px; }
.receipt-separator {
text-align: center;
margin: 12px 0;
font-size: 11px;
letter-spacing: -1px;
}
.receipt-info, .receipt-bets, .receipt-total, .receipt-footer { margin: 15px 0; }
.receipt-row, .receipt-bet-line, .receipt-total-line {
display: flex;
justify-content: space-between;
margin-bottom: 3px;
font-size: 11px;
}
.receipt-bet-item { margin-bottom: 12px; }
.receipt-match { font-weight: bold; font-size: 13px; text-align: center; }
.receipt-match-details { font-size: 11px; text-align: center; margin-bottom: 3px; }
.receipt-venue { font-size: 10px; text-align: center; margin-bottom: 4px; color: #666; }
.receipt-status { font-size: 10px; text-align: center; margin-top: 3px; font-weight: bold; }
.receipt-total {
border-top: 2px solid #000;
padding-top: 8px;
font-weight: bold;
font-size: 14px;
}
.receipt-verification { text-align: center; margin: 15px 0; }
.receipt-qr, .receipt-barcode { margin: 8px 0; text-align: center; }
.qr-image { width: 100px; height: 100px; border: 1px solid #ccc; }
.qr-text, .barcode-text { font-size: 10px; margin-top: 5px; }
.barcode-img { width: auto; height: auto; max-width: 200px; border: 1px solid #ccc; }
.receipt-footer {
text-align: center;
font-size: 10px;
margin-top: 15px;
border-top: 1px solid #000;
padding-top: 8px;
}
.receipt-timestamp { margin-top: 8px; font-size: 9px; color: #666; }
</style>
</head>
<body>
${printContent}
</body>
</html>
`);
printWindow.document.close();
printWindow.focus();
// Wait for images to load then print
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 500);
}).catch(error => {
console.error('Failed to mark bet as paid:', error);
// Still print even if marking as paid fails
const printContent = document.getElementById('thermal-receipt').innerHTML; const printContent = document.getElementById('thermal-receipt').innerHTML;
const printWindow = window.open('', '', 'height=600,width=400'); const printWindow = window.open('', '', 'height=600,width=400');
...@@ -921,6 +1032,7 @@ function printThermalReceipt() { ...@@ -921,6 +1032,7 @@ function printThermalReceipt() {
printWindow.print(); printWindow.print();
printWindow.close(); printWindow.close();
}, 500); }, 500);
});
} }
function generateBetVerificationQR() { function generateBetVerificationQR() {
...@@ -982,10 +1094,35 @@ function markBetAsPaid(betUuid) { ...@@ -982,10 +1094,35 @@ function markBetAsPaid(betUuid) {
} }
} }
function markBetAsPaidForPrinting(betUuid) {
return fetch(`/api/cashier/bets/${betUuid}/mark-paid`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Bet marked as paid for printing');
} else {
throw new Error(data.error || 'Failed to mark bet as paid');
}
});
}
function directPrintBet(betId) { function directPrintBet(betId) {
// Mark bet as paid before printing
markBetAsPaidForPrinting(betId).then(() => {
// Use the global bet data for direct printing // Use the global bet data for direct printing
const receiptHtml = generateReceiptHtml(window.betData); const receiptHtml = generateReceiptHtml(window.betData);
printDirectly(receiptHtml); printDirectly(receiptHtml);
}).catch(error => {
console.error('Failed to mark bet as paid:', error);
// Still print even if marking as paid fails
const receiptHtml = generateReceiptHtml(window.betData);
printDirectly(receiptHtml);
});
} }
function printDirectly(printContent) { function printDirectly(printContent) {
......
...@@ -508,7 +508,8 @@ function processBarcodeInput() { ...@@ -508,7 +508,8 @@ function processBarcodeInput() {
} }
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');
......
...@@ -489,7 +489,8 @@ ...@@ -489,7 +489,8 @@
} }
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