Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
MBetterd
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
MBetterd
Commits
6d547630
Commit
6d547630
authored
Feb 01, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update reports
parent
e3eef4f3
Changes
9
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1165 additions
and
53 deletions
+1165
-53
REPORTS_IMPLEMENTATION_GUIDE.md
REPORTS_IMPLEMENTATION_GUIDE.md
+455
-0
REPORTS_SYNC_PROTOCOL_DOCUMENTATION.md
REPORTS_SYNC_PROTOCOL_DOCUMENTATION.md
+415
-0
routes.py
app/api/routes.py
+31
-4
migrations.py
app/database/migrations.py
+56
-0
add_cap_compensation_balance.py
app/database/migrations/add_cap_compensation_balance.py
+49
-0
routes.py
app/main/routes.py
+88
-27
models.py
app/models.py
+4
-0
report_detail.html
app/templates/main/report_detail.html
+13
-9
reports.html
app/templates/main/reports.html
+54
-13
No files found.
REPORTS_IMPLEMENTATION_GUIDE.md
0 → 100644
View file @
6d547630
This diff is collapsed.
Click to expand it.
REPORTS_SYNC_PROTOCOL_DOCUMENTATION.md
0 → 100644
View file @
6d547630
This diff is collapsed.
Click to expand it.
app/api/routes.py
View file @
6d547630
...
@@ -1041,6 +1041,9 @@ def api_reports_sync():
...
@@ -1041,6 +1041,9 @@ def api_reports_sync():
'details'
:
f
'Missing required field: {field}'
'details'
:
f
'Missing required field: {field}'
}),
400
}),
400
# Get cap compensation balance (optional field)
cap_compensation_balance
=
data
.
get
(
'cap_compensation_balance'
,
0.00
)
# Check for duplicate sync_id (idempotency)
# Check for duplicate sync_id (idempotency)
existing_sync
=
ReportSync
.
query
.
filter_by
(
sync_id
=
data
[
'sync_id'
])
.
first
()
existing_sync
=
ReportSync
.
query
.
filter_by
(
sync_id
=
data
[
'sync_id'
])
.
first
()
if
existing_sync
:
if
existing_sync
:
...
@@ -1111,12 +1114,16 @@ def api_reports_sync():
...
@@ -1111,12 +1114,16 @@ def api_reports_sync():
total_payout
=
summary
[
'total_payout'
],
total_payout
=
summary
[
'total_payout'
],
net_profit
=
summary
[
'net_profit'
],
net_profit
=
summary
[
'net_profit'
],
total_bets
=
summary
[
'total_bets'
],
total_bets
=
summary
[
'total_bets'
],
total_matches
=
summary
[
'total_matches'
]
total_matches
=
summary
[
'total_matches'
],
cap_compensation_balance
=
cap_compensation_balance
)
)
db
.
session
.
add
(
report_sync
)
db
.
session
.
add
(
report_sync
)
# Process bets
# Process bets
bets_count
=
0
bets_count
=
0
bets_new
=
0
bets_duplicate
=
0
for
bet_data
in
data
[
'bets'
]:
for
bet_data
in
data
[
'bets'
]:
# Validate bet UUID
# Validate bet UUID
try
:
try
:
...
@@ -1131,7 +1138,26 @@ def api_reports_sync():
...
@@ -1131,7 +1138,26 @@ def api_reports_sync():
# Check for duplicate bet UUID
# Check for duplicate bet UUID
existing_bet
=
Bet
.
query
.
filter_by
(
uuid
=
bet_data
[
'uuid'
])
.
first
()
existing_bet
=
Bet
.
query
.
filter_by
(
uuid
=
bet_data
[
'uuid'
])
.
first
()
if
existing_bet
:
if
existing_bet
:
logger
.
warning
(
f
"Duplicate bet UUID {bet_data['uuid']} detected, skipping"
)
# Update existing bet if it has changed
bet_updated
=
False
if
existing_bet
.
total_amount
!=
bet_data
[
'total_amount'
]:
existing_bet
.
total_amount
=
bet_data
[
'total_amount'
]
bet_updated
=
True
if
existing_bet
.
paid
!=
bet_data
.
get
(
'paid'
,
False
):
existing_bet
.
paid
=
bet_data
.
get
(
'paid'
,
False
)
bet_updated
=
True
if
existing_bet
.
paid_out
!=
bet_data
.
get
(
'paid_out'
,
False
):
existing_bet
.
paid_out
=
bet_data
.
get
(
'paid_out'
,
False
)
bet_updated
=
True
if
bet_updated
:
existing_bet
.
sync_id
=
report_sync
.
id
bets_count
+=
1
bets_duplicate
+=
1
logger
.
info
(
f
"Updated existing bet {bet_data['uuid']}"
)
else
:
logger
.
warning
(
f
"Duplicate bet UUID {bet_data['uuid']} detected, skipping (no changes)"
)
bets_duplicate
+=
1
continue
continue
# Parse bet datetime
# Parse bet datetime
...
@@ -1160,6 +1186,7 @@ def api_reports_sync():
...
@@ -1160,6 +1186,7 @@ def api_reports_sync():
# Flush to get bet.id before creating bet details
# Flush to get bet.id before creating bet details
db
.
session
.
flush
()
db
.
session
.
flush
()
bets_count
+=
1
bets_count
+=
1
bets_new
+=
1
# Process bet details
# Process bet details
for
detail_data
in
bet_data
.
get
(
'details'
,
[]):
for
detail_data
in
bet_data
.
get
(
'details'
,
[]):
...
@@ -1250,8 +1277,8 @@ def api_reports_sync():
...
@@ -1250,8 +1277,8 @@ def api_reports_sync():
operation_type
=
'new_sync'
,
operation_type
=
'new_sync'
,
status
=
'success'
,
status
=
'success'
,
bets_processed
=
bets_count
,
bets_processed
=
bets_count
,
bets_new
=
bets_
count
,
bets_new
=
bets_
new
,
bets_duplicate
=
0
,
bets_duplicate
=
bets_duplicate
,
stats_processed
=
stats_count
,
stats_processed
=
stats_count
,
stats_new
=
stats_new
,
stats_new
=
stats_new
,
stats_updated
=
stats_updated
,
stats_updated
=
stats_updated
,
...
...
app/database/migrations.py
View file @
6d547630
...
@@ -775,6 +775,61 @@ class Migration_010_CreateReportsTables(Migration):
...
@@ -775,6 +775,61 @@ class Migration_010_CreateReportsTables(Migration):
def
can_rollback
(
self
)
->
bool
:
def
can_rollback
(
self
)
->
bool
:
return
True
return
True
class
Migration_011_AddCapCompensationBalance
(
Migration
):
"""Add cap_compensation_balance column to report_syncs table"""
def
__init__
(
self
):
super
()
.
__init__
(
"011"
,
"Add cap_compensation_balance column to report_syncs table"
)
def
up
(
self
):
"""Add cap_compensation_balance column"""
try
:
# Check if column already exists
inspector
=
inspect
(
db
.
engine
)
# Check if report_syncs table exists
if
'report_syncs'
not
in
inspector
.
get_table_names
():
logger
.
info
(
"report_syncs table does not exist yet, skipping migration"
)
return
True
columns
=
[
col
[
'name'
]
for
col
in
inspector
.
get_columns
(
'report_syncs'
)]
if
'cap_compensation_balance'
in
columns
:
logger
.
info
(
"cap_compensation_balance column already exists, skipping creation"
)
return
True
# Add column
alter_table_sql
=
'''
ALTER TABLE report_syncs
ADD COLUMN cap_compensation_balance DECIMAL(15,2) NOT NULL DEFAULT 0.00
'''
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
text
(
alter_table_sql
))
conn
.
commit
()
logger
.
info
(
"Added cap_compensation_balance column successfully"
)
return
True
except
Exception
as
e
:
logger
.
error
(
f
"Migration 011 failed: {str(e)}"
)
raise
def
down
(
self
):
"""Drop cap_compensation_balance column"""
try
:
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
text
(
"ALTER TABLE report_syncs DROP COLUMN IF EXISTS cap_compensation_balance"
))
conn
.
commit
()
logger
.
info
(
"Dropped cap_compensation_balance column"
)
return
True
except
Exception
as
e
:
logger
.
error
(
f
"Rollback of migration 011 failed: {str(e)}"
)
raise
def
can_rollback
(
self
)
->
bool
:
return
True
class
MigrationManager
:
class
MigrationManager
:
"""Manages database migrations and versioning"""
"""Manages database migrations and versioning"""
...
@@ -790,6 +845,7 @@ class MigrationManager:
...
@@ -790,6 +845,7 @@ class MigrationManager:
Migration_008_AddRemoteDomainSetting
(),
Migration_008_AddRemoteDomainSetting
(),
Migration_009_CreateClientActivityTable
(),
Migration_009_CreateClientActivityTable
(),
Migration_010_CreateReportsTables
(),
Migration_010_CreateReportsTables
(),
Migration_011_AddCapCompensationBalance
(),
]
]
def
ensure_version_table
(
self
):
def
ensure_version_table
(
self
):
...
...
app/database/migrations/add_cap_compensation_balance.py
0 → 100644
View file @
6d547630
"""
Migration to add cap_compensation_balance field to report_syncs table
"""
from
datetime
import
datetime
from
app
import
db
from
app.models
import
ReportSync
def
upgrade
():
"""Add cap_compensation_balance column to report_syncs table"""
try
:
# Check if column already exists
inspector
=
db
.
inspect
(
db
.
engine
)
columns
=
[
col
[
'name'
]
for
col
in
inspector
.
get_columns
(
'report_syncs'
)]
if
'cap_compensation_balance'
not
in
columns
:
# Add the column
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE report_syncs
ADD COLUMN cap_compensation_balance NUMERIC(15, 2) DEFAULT 0.00
"""
))
db
.
session
.
commit
()
print
(
"Successfully added cap_compensation_balance column to report_syncs table"
)
else
:
print
(
"cap_compensation_balance column already exists in report_syncs table"
)
except
Exception
as
e
:
db
.
session
.
rollback
()
print
(
f
"Error adding cap_compensation_balance column: {str(e)}"
)
raise
def
downgrade
():
"""Remove cap_compensation_balance column from report_syncs table"""
try
:
with
db
.
engine
.
connect
()
as
conn
:
conn
.
execute
(
db
.
text
(
"""
ALTER TABLE report_syncs
DROP COLUMN IF EXISTS cap_compensation_balance
"""
))
db
.
session
.
commit
()
print
(
"Successfully removed cap_compensation_balance column from report_syncs table"
)
except
Exception
as
e
:
db
.
session
.
rollback
()
print
(
f
"Error removing cap_compensation_balance column: {str(e)}"
)
raise
if
__name__
==
'__main__'
:
print
(
"Running migration: add_cap_compensation_balance"
)
upgrade
()
\ No newline at end of file
app/main/routes.py
View file @
6d547630
...
@@ -1541,14 +1541,16 @@ def clients():
...
@@ -1541,14 +1541,16 @@ def clients():
def
reports
():
def
reports
():
"""Reports page with filtering, pagination, and export"""
"""Reports page with filtering, pagination, and export"""
try
:
try
:
from
app.models
import
ReportSync
,
Bet
,
ExtractionStats
from
app.models
import
ReportSync
,
Bet
,
ExtractionStats
,
APIToken
,
ClientActivity
from
sqlalchemy
import
func
,
and_
,
or_
from
sqlalchemy
import
func
,
and_
,
or_
# Get filter parameters
# Get filter parameters
client_id_filter
=
request
.
args
.
get
(
'client_id'
,
''
)
.
strip
()
client_id_filter
=
request
.
args
.
get
(
'client_id'
,
''
)
.
strip
()
date_range_filter
=
request
.
args
.
get
(
'date_range'
,
''
)
.
strip
()
date_range_filter
=
request
.
args
.
get
(
'date_range'
,
'
today
'
)
.
strip
()
start_date_filter
=
request
.
args
.
get
(
'start_date'
,
''
)
.
strip
()
start_date_filter
=
request
.
args
.
get
(
'start_date'
,
''
)
.
strip
()
end_date_filter
=
request
.
args
.
get
(
'end_date'
,
''
)
.
strip
()
end_date_filter
=
request
.
args
.
get
(
'end_date'
,
''
)
.
strip
()
start_time_filter
=
request
.
args
.
get
(
'start_time'
,
''
)
.
strip
()
end_time_filter
=
request
.
args
.
get
(
'end_time'
,
''
)
.
strip
()
sort_by
=
request
.
args
.
get
(
'sort_by'
,
'sync_timestamp'
)
sort_by
=
request
.
args
.
get
(
'sort_by'
,
'sync_timestamp'
)
sort_order
=
request
.
args
.
get
(
'sort_order'
,
'desc'
)
sort_order
=
request
.
args
.
get
(
'sort_order'
,
'desc'
)
export_format
=
request
.
args
.
get
(
'export'
,
''
)
.
strip
()
export_format
=
request
.
args
.
get
(
'export'
,
''
)
.
strip
()
...
@@ -1557,16 +1559,66 @@ def reports():
...
@@ -1557,16 +1559,66 @@ def reports():
page
=
request
.
args
.
get
(
'page'
,
1
,
type
=
int
)
page
=
request
.
args
.
get
(
'page'
,
1
,
type
=
int
)
per_page
=
min
(
request
.
args
.
get
(
'per_page'
,
20
,
type
=
int
),
100
)
per_page
=
min
(
request
.
args
.
get
(
'per_page'
,
20
,
type
=
int
),
100
)
# Calculate date range based on filter
now
=
datetime
.
utcnow
()
start_date
=
None
end_date
=
None
if
date_range_filter
==
'today'
:
start_date
=
now
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
now
elif
date_range_filter
==
'yesterday'
:
yesterday
=
now
-
timedelta
(
days
=
1
)
start_date
=
yesterday
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
yesterday
.
replace
(
hour
=
23
,
minute
=
59
,
second
=
59
,
microsecond
=
999999
)
elif
date_range_filter
==
'this_week'
:
# Start of current week (Monday)
start_date
=
now
-
timedelta
(
days
=
now
.
weekday
())
start_date
=
start_date
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
now
elif
date_range_filter
==
'last_week'
:
# Start of last week (Monday)
last_week_end
=
now
-
timedelta
(
days
=
now
.
weekday
()
+
1
)
last_week_start
=
last_week_end
-
timedelta
(
days
=
6
)
start_date
=
last_week_start
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
last_week_end
.
replace
(
hour
=
23
,
minute
=
59
,
second
=
59
,
microsecond
=
999999
)
elif
date_range_filter
==
'this_month'
:
start_date
=
now
.
replace
(
day
=
1
,
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
now
elif
date_range_filter
==
'all'
:
start_date
=
None
end_date
=
None
elif
date_range_filter
==
'custom'
:
# Use custom date range
if
start_date_filter
:
try
:
start_date
=
datetime
.
strptime
(
start_date_filter
,
'
%
Y-
%
m-
%
d'
)
if
start_time_filter
:
hour
,
minute
=
map
(
int
,
start_time_filter
.
split
(
':'
))
start_date
=
start_date
.
replace
(
hour
=
hour
,
minute
=
minute
,
second
=
0
,
microsecond
=
0
)
else
:
start_date
=
start_date
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
except
ValueError
:
pass
if
end_date_filter
:
try
:
end_date
=
datetime
.
strptime
(
end_date_filter
,
'
%
Y-
%
m-
%
d'
)
if
end_time_filter
:
hour
,
minute
=
map
(
int
,
end_time_filter
.
split
(
':'
))
end_date
=
end_date
.
replace
(
hour
=
hour
,
minute
=
minute
,
second
=
59
,
microsecond
=
999999
)
else
:
end_date
=
end_date
.
replace
(
hour
=
23
,
minute
=
59
,
second
=
59
,
microsecond
=
999999
)
except
ValueError
:
pass
# Base query
# Base query
if
current_user
.
is_admin
:
if
current_user
.
is_admin
:
query
=
ReportSync
.
query
query
=
ReportSync
.
query
else
:
else
:
# Non-admin users can only see reports from their own clients
# Non-admin users can only see reports from their own clients
from
app.models
import
APIToken
user_token_ids
=
[
t
.
id
for
t
in
APIToken
.
query
.
filter_by
(
user_id
=
current_user
.
id
)
.
all
()]
user_token_ids
=
[
t
.
id
for
t
in
APIToken
.
query
.
filter_by
(
user_id
=
current_user
.
id
)
.
all
()]
if
user_token_ids
:
if
user_token_ids
:
# Get client_ids from ClientActivity for this user's tokens
# Get client_ids from ClientActivity for this user's tokens
from
app.models
import
ClientActivity
client_ids
=
[
c
.
rustdesk_id
for
c
in
ClientActivity
.
query
.
filter
(
client_ids
=
[
c
.
rustdesk_id
for
c
in
ClientActivity
.
query
.
filter
(
ClientActivity
.
api_token_id
.
in_
(
user_token_ids
)
ClientActivity
.
api_token_id
.
in_
(
user_token_ids
)
)
.
all
()]
)
.
all
()]
...
@@ -1581,21 +1633,11 @@ def reports():
...
@@ -1581,21 +1633,11 @@ def reports():
if
client_id_filter
:
if
client_id_filter
:
query
=
query
.
filter
(
ReportSync
.
client_id
==
client_id_filter
)
query
=
query
.
filter
(
ReportSync
.
client_id
==
client_id_filter
)
if
start_date_filter
:
if
start_date
:
try
:
start_date
=
datetime
.
strptime
(
start_date_filter
,
'
%
Y-
%
m-
%
d'
)
query
=
query
.
filter
(
ReportSync
.
start_date
>=
start_date
)
query
=
query
.
filter
(
ReportSync
.
start_date
>=
start_date
)
except
ValueError
:
pass
if
end_date_filter
:
if
end_date
:
try
:
end_date
=
datetime
.
strptime
(
end_date_filter
,
'
%
Y-
%
m-
%
d'
)
# Include the entire end date
end_date
=
end_date
.
replace
(
hour
=
23
,
minute
=
59
,
second
=
59
)
query
=
query
.
filter
(
ReportSync
.
end_date
<=
end_date
)
query
=
query
.
filter
(
ReportSync
.
end_date
<=
end_date
)
except
ValueError
:
pass
# Sorting
# Sorting
if
hasattr
(
ReportSync
,
sort_by
):
if
hasattr
(
ReportSync
,
sort_by
):
...
@@ -1614,29 +1656,48 @@ def reports():
...
@@ -1614,29 +1656,48 @@ def reports():
# Pagination
# Pagination
reports_pagination
=
query
.
paginate
(
page
=
page
,
per_page
=
per_page
,
error_out
=
False
)
reports_pagination
=
query
.
paginate
(
page
=
page
,
per_page
=
per_page
,
error_out
=
False
)
# Get unique client IDs for filter dropdown
# Get unique client IDs for filter dropdown
with token names
if
current_user
.
is_admin
:
if
current_user
.
is_admin
:
all_client_ids
=
db
.
session
.
query
(
ReportSync
.
client_id
)
.
distinct
()
.
all
()
# Get all clients with their token names
clients_query
=
db
.
session
.
query
(
ReportSync
.
client_id
,
APIToken
.
name
.
label
(
'token_name'
)
)
.
join
(
ClientActivity
,
ReportSync
.
client_id
==
ClientActivity
.
rustdesk_id
)
.
join
(
APIToken
,
ClientActivity
.
api_token_id
==
APIToken
.
id
)
.
filter
(
APIToken
.
is_active
==
True
)
.
distinct
()
.
all
()
client_data
=
[{
'client_id'
:
c
.
client_id
,
'token_name'
:
c
.
token_name
}
for
c
in
clients_query
]
else
:
else
:
if
user_token_ids
:
if
user_token_ids
:
from
app.models
import
ClientActivity
clients_query
=
db
.
session
.
query
(
all_client_ids
=
db
.
session
.
query
(
ClientActivity
.
rustdesk_id
)
.
filter
(
ClientActivity
.
rustdesk_id
.
label
(
'client_id'
),
ClientActivity
.
api_token_id
.
in_
(
user_token_ids
)
APIToken
.
name
.
label
(
'token_name'
)
)
.
join
(
APIToken
,
ClientActivity
.
api_token_id
==
APIToken
.
id
)
.
filter
(
ClientActivity
.
api_token_id
.
in_
(
user_token_ids
),
APIToken
.
is_active
==
True
)
.
distinct
()
.
all
()
)
.
distinct
()
.
all
()
else
:
all_client_ids
=
[]
client_ids
=
[
cid
[
0
]
for
cid
in
all_client_ids
if
cid
[
0
]]
client_data
=
[{
'client_id'
:
c
.
client_id
,
'token_name'
:
c
.
token_name
}
for
c
in
clients_query
]
else
:
client_data
=
[]
return
render_template
(
'main/reports.html'
,
return
render_template
(
'main/reports.html'
,
reports
=
reports_pagination
.
items
,
reports
=
reports_pagination
.
items
,
pagination
=
reports_pagination
,
pagination
=
reports_pagination
,
client_
ids
=
client_ids
,
client_
data
=
client_data
,
filters
=
{
filters
=
{
'client_id'
:
client_id_filter
,
'client_id'
:
client_id_filter
,
'date_range'
:
date_range_filter
,
'date_range'
:
date_range_filter
,
'start_date'
:
start_date_filter
,
'start_date'
:
start_date_filter
,
'end_date'
:
end_date_filter
,
'end_date'
:
end_date_filter
,
'start_time'
:
start_time_filter
,
'end_time'
:
end_time_filter
,
'sort_by'
:
sort_by
,
'sort_by'
:
sort_by
,
'sort_order'
:
sort_order
'sort_order'
:
sort_order
})
})
...
...
app/models.py
View file @
6d547630
...
@@ -857,6 +857,9 @@ class ReportSync(db.Model):
...
@@ -857,6 +857,9 @@ class ReportSync(db.Model):
total_bets
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
total_bets
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
total_matches
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
total_matches
=
db
.
Column
(
db
.
Integer
,
default
=
0
)
# Cap compensation balance at the time of sync
cap_compensation_balance
=
db
.
Column
(
db
.
Numeric
(
15
,
2
),
default
=
0.00
)
# Metadata
# Metadata
created_at
=
db
.
Column
(
db
.
DateTime
,
default
=
datetime
.
utcnow
)
created_at
=
db
.
Column
(
db
.
DateTime
,
default
=
datetime
.
utcnow
)
...
@@ -879,6 +882,7 @@ class ReportSync(db.Model):
...
@@ -879,6 +882,7 @@ class ReportSync(db.Model):
'net_profit'
:
float
(
self
.
net_profit
)
if
self
.
net_profit
else
0.0
,
'net_profit'
:
float
(
self
.
net_profit
)
if
self
.
net_profit
else
0.0
,
'total_bets'
:
self
.
total_bets
,
'total_bets'
:
self
.
total_bets
,
'total_matches'
:
self
.
total_matches
,
'total_matches'
:
self
.
total_matches
,
'cap_compensation_balance'
:
float
(
self
.
cap_compensation_balance
)
if
self
.
cap_compensation_balance
else
0.0
,
'created_at'
:
self
.
created_at
.
isoformat
()
if
self
.
created_at
else
None
'created_at'
:
self
.
created_at
.
isoformat
()
if
self
.
created_at
else
None
}
}
...
...
app/templates/main/report_detail.html
View file @
6d547630
...
@@ -80,22 +80,28 @@
...
@@ -80,22 +80,28 @@
</div>
</div>
</div>
</div>
<div
class=
"row mt-3"
>
<div
class=
"row mt-3"
>
<div
class=
"col-md-
4
"
>
<div
class=
"col-md-
3
"
>
<div
class=
"p-3 bg-primary text-white rounded"
>
<div
class=
"p-3 bg-primary text-white rounded"
>
<h6
class=
"mb-1"
>
Total Payin
</h6>
<h6
class=
"mb-1"
>
Total Payin
</h6>
<h3
class=
"mb-0"
>
{{ "{:,.2f}".format(report.total_payin) if report.total_payin else '0.00' }}
</h3>
<h3
class=
"mb-0"
>
{{ "{:,.2f}".format(report.total_payin) if report.total_payin else '0.00' }}
</h3>
</div>
</div>
</div>
</div>
<div
class=
"col-md-
4
"
>
<div
class=
"col-md-
3
"
>
<div
class=
"p-3 bg-info text-white rounded"
>
<div
class=
"p-3 bg-info text-white rounded"
>
<h6
class=
"mb-1"
>
Total Payout
</h6>
<h6
class=
"mb-1"
>
Total Payout
</h6>
<h3
class=
"mb-0"
>
{{ "{:,.2f}".format(report.total_payout) if report.total_payout else '0.00' }}
</h3>
<h3
class=
"mb-0"
>
{{ "{:,.2f}".format(report.total_payout) if report.total_payout else '0.00' }}
</h3>
</div>
</div>
</div>
</div>
<div
class=
"col-md-4"
>
<div
class=
"col-md-3"
>
<div
class=
"p-3 {% if report.net_profit >= 0 %}bg-success{% else %}bg-danger{% endif %} text-white rounded"
>
<div
class=
"p-3 {% if (report.total_payin - report.total_payout) >= 0 %}bg-success{% else %}bg-danger{% endif %} text-white rounded"
>
<h6
class=
"mb-1"
>
Net Profit
</h6>
<h6
class=
"mb-1"
>
Balance
</h6>
<h3
class=
"mb-0"
>
{{ "{:,.2f}".format(report.net_profit) if report.net_profit else '0.00' }}
</h3>
<h3
class=
"mb-0"
>
{{ "{:,.2f}".format(report.total_payin - report.total_payout) if report.total_payin and report.total_payout else '0.00' }}
</h3>
</div>
</div>
<div
class=
"col-md-3"
>
<div
class=
"p-3 bg-warning text-white rounded"
>
<h6
class=
"mb-1"
>
CAP Redistribution Balance
</h6>
<h3
class=
"mb-0"
>
{{ "{:,.2f}".format(report.cap_compensation_balance) if report.cap_compensation_balance else '0.00' }}
</h3>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -189,7 +195,7 @@
...
@@ -189,7 +195,7 @@
<td
class=
"text-end"
>
{{ "{:,.2f}".format(detail.amount) if detail.amount else '0.00' }}
</td>
<td
class=
"text-end"
>
{{ "{:,.2f}".format(detail.amount) if detail.amount else '0.00' }}
</td>
<td
class=
"text-end"
>
{{ "{:,.2f}".format(detail.win_amount) if detail.win_amount else '0.00' }}
</td>
<td
class=
"text-end"
>
{{ "{:,.2f}".format(detail.win_amount) if detail.win_amount else '0.00' }}
</td>
<td>
<td>
<span
class=
"badge bg-{% if detail.result == 'w
o
n' %}success{% elif detail.result == 'lost' %}danger{% elif detail.result == 'pending' %}warning{% else %}secondary{% endif %}"
>
<span
class=
"badge bg-{% if detail.result == 'w
i
n' %}success{% elif detail.result == 'lost' %}danger{% elif detail.result == 'pending' %}warning{% else %}secondary{% endif %}"
>
{{ detail.result }}
{{ detail.result }}
</span>
</span>
</td>
</td>
...
@@ -295,7 +301,6 @@
...
@@ -295,7 +301,6 @@
<th>
Outcome
</th>
<th>
Outcome
</th>
<th>
Bets
</th>
<th>
Bets
</th>
<th>
Amount
</th>
<th>
Amount
</th>
<th>
Coefficient
</th>
</tr>
</tr>
</thead>
</thead>
<tbody>
<tbody>
...
@@ -304,7 +309,6 @@
...
@@ -304,7 +309,6 @@
<td>
{{ outcome }}
</td>
<td>
{{ outcome }}
</td>
<td>
{{ data.bets }}
</td>
<td>
{{ data.bets }}
</td>
<td>
{{ "{:,.2f}".format(data.amount) if data.amount else '0.00' }}
</td>
<td>
{{ "{:,.2f}".format(data.amount) if data.amount else '0.00' }}
</td>
<td>
{{ data.coefficient }}
</td>
</tr>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</tbody>
...
...
app/templates/main/reports.html
View file @
6d547630
...
@@ -28,33 +28,43 @@
...
@@ -28,33 +28,43 @@
<div
class=
"card-body"
>
<div
class=
"card-body"
>
<form
method=
"GET"
action=
"{{ url_for('main.reports') }}"
class=
"row g-3"
>
<form
method=
"GET"
action=
"{{ url_for('main.reports') }}"
class=
"row g-3"
>
<div
class=
"col-md-3"
>
<div
class=
"col-md-3"
>
<label
for=
"client_id"
class=
"form-label"
>
Client
ID
</label>
<label
for=
"client_id"
class=
"form-label"
>
Client
</label>
<select
class=
"form-select"
id=
"client_id"
name=
"client_id"
>
<select
class=
"form-select"
id=
"client_id"
name=
"client_id"
>
<option
value=
""
>
All Clients
</option>
<option
value=
""
>
All Clients
</option>
{% for c
id in client_ids
%}
{% for c
lient in client_data
%}
<option
value=
"{{ c
id }}"
{%
if
filters
.
client_id =
=
cid
%}
selected
{%
endif
%}
>
{{ cid }}
</option>
<option
value=
"{{ c
lient.client_id }}"
{%
if
filters
.
client_id =
=
client
.
client_id
%}
selected
{%
endif
%}
>
{{ client.token_name }} ({{ client.client_id }})
</option>
{% endfor %}
{% endfor %}
</select>
</select>
</div>
</div>
<div
class=
"col-md-2"
>
<div
class=
"col-md-2"
>
<label
for=
"date_range"
class=
"form-label"
>
Date Range
</label>
<label
for=
"date_range"
class=
"form-label"
>
Date Range
</label>
<select
class=
"form-select"
id=
"date_range"
name=
"date_range"
>
<select
class=
"form-select"
id=
"date_range"
name=
"date_range"
onchange=
"toggleCustomDateRange()"
>
<option
value=
""
>
All
</option>
<option
value=
"today"
{%
if
filters
.
date_range =
=
'
today
'
%}
selected
{%
endif
%}
>
Today
</option>
<option
value=
"today"
{%
if
filters
.
date_range =
=
'
today
'
%}
selected
{%
endif
%}
>
Today
</option>
<option
value=
"yesterday"
{%
if
filters
.
date_range =
=
'
yesterday
'
%}
selected
{%
endif
%}
>
Yesterday
</option>
<option
value=
"yesterday"
{%
if
filters
.
date_range =
=
'
yesterday
'
%}
selected
{%
endif
%}
>
Yesterday
</option>
<option
value=
"week"
{%
if
filters
.
date_range =
=
'
week
'
%}
selected
{%
endif
%}
>
This Week
</option>
<option
value=
"this_week"
{%
if
filters
.
date_range =
=
'
this_week
'
%}
selected
{%
endif
%}
>
This Week
</option>
<option
value=
"last_week"
{%
if
filters
.
date_range =
=
'
last_week
'
%}
selected
{%
endif
%}
>
Last Week
</option>
<option
value=
"this_month"
{%
if
filters
.
date_range =
=
'
this_month
'
%}
selected
{%
endif
%}
>
This Month
</option>
<option
value=
"all"
{%
if
filters
.
date_range =
=
'
all
'
%}
selected
{%
endif
%}
>
All Time
</option>
<option
value=
"all"
{%
if
filters
.
date_range =
=
'
all
'
%}
selected
{%
endif
%}
>
All Time
</option>
<option
value=
"custom"
{%
if
filters
.
date_range =
=
'
custom
'
%}
selected
{%
endif
%}
>
Custom
</option>
</select>
</select>
</div>
</div>
<div
class=
"col-md-2"
>
<div
class=
"col-md-2"
id=
"custom-date-fields"
style=
"display: none;"
>
<label
for=
"start_date"
class=
"form-label"
>
Start Date
</label>
<label
for=
"start_date"
class=
"form-label"
>
Start Date
</label>
<input
type=
"date"
class=
"form-control"
id=
"start_date"
name=
"start_date"
value=
"{{ filters.start_date }}"
>
<input
type=
"date"
class=
"form-control"
id=
"start_date"
name=
"start_date"
value=
"{{ filters.start_date }}"
>
</div>
</div>
<div
class=
"col-md-2"
>
<div
class=
"col-md-1"
id=
"custom-time-fields"
style=
"display: none;"
>
<label
for=
"start_time"
class=
"form-label"
>
Start Time
</label>
<input
type=
"time"
class=
"form-control"
id=
"start_time"
name=
"start_time"
value=
"{{ filters.start_time }}"
>
</div>
<div
class=
"col-md-2"
id=
"custom-date-fields-end"
style=
"display: none;"
>
<label
for=
"end_date"
class=
"form-label"
>
End Date
</label>
<label
for=
"end_date"
class=
"form-label"
>
End Date
</label>
<input
type=
"date"
class=
"form-control"
id=
"end_date"
name=
"end_date"
value=
"{{ filters.end_date }}"
>
<input
type=
"date"
class=
"form-control"
id=
"end_date"
name=
"end_date"
value=
"{{ filters.end_date }}"
>
</div>
</div>
<div
class=
"col-md-2"
>
<div
class=
"col-md-1"
id=
"custom-time-fields-end"
style=
"display: none;"
>
<label
for=
"end_time"
class=
"form-label"
>
End Time
</label>
<input
type=
"time"
class=
"form-control"
id=
"end_time"
name=
"end_time"
value=
"{{ filters.end_time }}"
>
</div>
<div
class=
"col-md-1"
>
<label
for=
"sort_by"
class=
"form-label"
>
Sort By
</label>
<label
for=
"sort_by"
class=
"form-label"
>
Sort By
</label>
<select
class=
"form-select"
id=
"sort_by"
name=
"sort_by"
>
<select
class=
"form-select"
id=
"sort_by"
name=
"sort_by"
>
<option
value=
"sync_timestamp"
{%
if
filters
.
sort_by =
=
'
sync_timestamp
'
%}
selected
{%
endif
%}
>
Sync Timestamp
</option>
<option
value=
"sync_timestamp"
{%
if
filters
.
sort_by =
=
'
sync_timestamp
'
%}
selected
{%
endif
%}
>
Sync Timestamp
</option>
...
@@ -93,12 +103,13 @@
...
@@ -93,12 +103,13 @@
<thead>
<thead>
<tr>
<tr>
<th>
Sync ID
</th>
<th>
Sync ID
</th>
<th>
Client
ID
</th>
<th>
Client
</th>
<th>
Sync Timestamp
</th>
<th>
Sync Timestamp
</th>
<th>
Date Range
</th>
<th>
Date Range
</th>
<th>
Total Payin
</th>
<th>
Total Payin
</th>
<th>
Total Payout
</th>
<th>
Total Payout
</th>
<th>
Net Profit
</th>
<th>
Balance
</th>
<th>
CAP Balance
</th>
<th>
Total Bets
</th>
<th>
Total Bets
</th>
<th>
Total Matches
</th>
<th>
Total Matches
</th>
<th>
Actions
</th>
<th>
Actions
</th>
...
@@ -113,8 +124,11 @@
...
@@ -113,8 +124,11 @@
<td>
{{ report.date_range }}
</td>
<td>
{{ report.date_range }}
</td>
<td
class=
"text-end"
>
{{ "{:,.2f}".format(report.total_payin) if report.total_payin else '0.00' }}
</td>
<td
class=
"text-end"
>
{{ "{:,.2f}".format(report.total_payin) if report.total_payin else '0.00' }}
</td>
<td
class=
"text-end"
>
{{ "{:,.2f}".format(report.total_payout) if report.total_payout else '0.00' }}
</td>
<td
class=
"text-end"
>
{{ "{:,.2f}".format(report.total_payout) if report.total_payout else '0.00' }}
</td>
<td
class=
"text-end {% if report.net_profit >= 0 %}text-success{% else %}text-danger{% endif %}"
>
<td
class=
"text-end {% if (report.total_payin - report.total_payout) >= 0 %}text-success{% else %}text-danger{% endif %}"
>
{{ "{:,.2f}".format(report.net_profit) if report.net_profit else '0.00' }}
{{ "{:,.2f}".format(report.total_payin - report.total_payout) if report.total_payin and report.total_payout else '0.00' }}
</td>
<td
class=
"text-end text-info"
>
{{ "{:,.2f}".format(report.cap_compensation_balance) if report.cap_compensation_balance else '0.00' }}
</td>
</td>
<td
class=
"text-center"
>
{{ report.total_bets }}
</td>
<td
class=
"text-center"
>
{{ report.total_bets }}
</td>
<td
class=
"text-center"
>
{{ report.total_matches }}
</td>
<td
class=
"text-center"
>
{{ report.total_matches }}
</td>
...
@@ -178,4 +192,31 @@
...
@@ -178,4 +192,31 @@
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function
toggleCustomDateRange
()
{
var
dateRange
=
document
.
getElementById
(
'date_range'
).
value
;
var
customDateFields
=
document
.
getElementById
(
'custom-date-fields'
);
var
customTimeFields
=
document
.
getElementById
(
'custom-time-fields'
);
var
customDateFieldsEnd
=
document
.
getElementById
(
'custom-date-fields-end'
);
var
customTimeFieldsEnd
=
document
.
getElementById
(
'custom-time-fields-end'
);
if
(
dateRange
===
'custom'
)
{
customDateFields
.
style
.
display
=
'block'
;
customTimeFields
.
style
.
display
=
'block'
;
customDateFieldsEnd
.
style
.
display
=
'block'
;
customTimeFieldsEnd
.
style
.
display
=
'block'
;
}
else
{
customDateFields
.
style
.
display
=
'none'
;
customTimeFields
.
style
.
display
=
'none'
;
customDateFieldsEnd
.
style
.
display
=
'none'
;
customTimeFieldsEnd
.
style
.
display
=
'none'
;
}
}
// Initialize on page load
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
toggleCustomDateRange
();
});
</script>
{% endblock %}
{% endblock %}
\ No newline at end of file
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