Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
MBetterc
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mbetter
MBetterc
Commits
27663e61
Commit
27663e61
authored
Dec 12, 2025
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix barcode bet verification
parent
9ed29d92
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
728 additions
and
216 deletions
+728
-216
migrations.py
mbetterclient/database/migrations.py
+68
-0
models.py
mbetterclient/database/models.py
+9
-2
routes.py
mbetterclient/web_dashboard/routes.py
+120
-5
admin_bet_details.html
.../web_dashboard/templates/dashboard/admin_bet_details.html
+239
-102
bet_details.html
...client/web_dashboard/templates/dashboard/bet_details.html
+238
-101
verify_bet.html
...rclient/web_dashboard/templates/dashboard/verify_bet.html
+27
-3
verify_bet_mobile.html
.../web_dashboard/templates/dashboard/verify_bet_mobile.html
+27
-3
No files found.
mbetterclient/database/migrations.py
View file @
27663e61
...
...
@@ -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
(),
]
...
...
mbetterclient/database/models.py
View file @
27663e61
...
...
@@ -678,7 +678,10 @@ 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'
)
...
...
@@ -687,6 +690,10 @@ class BetModel(BaseModel):
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'
)
...
...
mbetterclient/web_dashboard/routes.py
View file @
27663e61
...
...
@@ -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"
)
...
...
@@ -4464,6 +4507,78 @@ 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
:
...
...
mbetterclient/web_dashboard/templates/dashboard/admin_bet_details.html
View file @
27663e61
...
...
@@ -445,6 +445,8 @@
"venue"
:
"{{ detail.match.venue_kampala_township if detail.match else 'Unknown' }}"
,
"outcome"
:
"{{ detail.outcome }}"
,
"amount"
:
{{
detail
.
amount
|
round
(
2
)
}},
"odds"
:
{{
detail
.
odds
|
round
(
2
)
}},
"potential_winning"
:
{{
detail
.
potential_winning
|
round
(
2
)
}},
"result"
:
"{{ detail.result }}"
}{
%
if
not
loop
.
last
%
},{
%
endif
%
}
{
%
endfor
%
}
...
...
@@ -628,6 +630,10 @@ function generateReceiptHtml(betData) {
<span>OUTCOME:
${
detail
.
outcome
}
</span>
<span>
${
formatCurrency
(
parseFloat
(
detail
.
amount
))}
</span>
</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">
STATUS:
${
detail
.
result
.
toUpperCase
()}
</div>
...
...
@@ -774,6 +780,111 @@ function generateVerificationCodes(betUuid) {
}
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
printWindow
=
window
.
open
(
''
,
''
,
'height=600,width=400'
);
...
...
@@ -874,6 +985,7 @@ function printThermalReceipt() {
printWindow
.
print
();
printWindow
.
close
();
},
500
);
});
}
function
deleteBetDetail
(
detailId
)
{
...
...
@@ -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
)
{
// Mark bet as paid before printing
markBetAsPaidForPrinting
(
betId
).
then
(()
=>
{
// Use the global bet data for direct printing
const
receiptHtml
=
generateReceiptHtml
(
window
.
betData
);
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
)
{
...
...
mbetterclient/web_dashboard/templates/dashboard/bet_details.html
View file @
27663e61
...
...
@@ -445,6 +445,8 @@
"venue"
:
"{{ detail.match.venue_kampala_township if detail.match else 'Unknown' }}"
,
"outcome"
:
"{{ detail.outcome }}"
,
"amount"
:
{{
detail
.
amount
|
round
(
2
)
}},
"odds"
:
{{
detail
.
odds
|
round
(
2
)
}},
"potential_winning"
:
{{
detail
.
potential_winning
|
round
(
2
)
}},
"result"
:
"{{ detail.result }}"
}{
%
if
not
loop
.
last
%
},{
%
endif
%
}
{
%
endfor
%
}
...
...
@@ -685,6 +687,10 @@ function generateReceiptHtml(betData) {
<span>OUTCOME:
${
detail
.
outcome
}
</span>
<span>
${
formatCurrency
(
parseFloat
(
detail
.
amount
))}
</span>
</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">
STATUS:
${
detail
.
result
.
toUpperCase
()}
</div>
...
...
@@ -821,6 +827,111 @@ function generateVerificationCodes(betUuid) {
}
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
printWindow
=
window
.
open
(
''
,
''
,
'height=600,width=400'
);
...
...
@@ -921,6 +1032,7 @@ function printThermalReceipt() {
printWindow
.
print
();
printWindow
.
close
();
},
500
);
});
}
function
generateBetVerificationQR
()
{
...
...
@@ -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
)
{
// Mark bet as paid before printing
markBetAsPaidForPrinting
(
betId
).
then
(()
=>
{
// Use the global bet data for direct printing
const
receiptHtml
=
generateReceiptHtml
(
window
.
betData
);
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
)
{
...
...
mbetterclient/web_dashboard/templates/dashboard/verify_bet.html
View file @
27663e61
...
...
@@ -508,7 +508,8 @@ function processBarcodeInput() {
}
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'
);
...
...
mbetterclient/web_dashboard/templates/dashboard/verify_bet_mobile.html
View file @
27663e61
...
...
@@ -489,7 +489,8 @@
}
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'
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment