Implement MatchReport update for incremental sync

parent e69c15ea
...@@ -1197,14 +1197,30 @@ def api_reports_sync(): ...@@ -1197,14 +1197,30 @@ def api_reports_sync():
}), 400 }), 400
# Create bet record with match_id and match_number from first detail # Create bet record with match_id and match_number from first detail
first_detail = bet_data.get('details', [{}])[0] if bet_data.get('details') else {} # Ensure we get a valid match_id from details
details = bet_data.get('details', [])
first_detail = details[0] if details else {}
# Get match_id from first detail that has it
match_id = None
match_number = None
for detail in details:
if detail.get('match_id'):
match_id = detail.get('match_id')
match_number = detail.get('match_number')
break
if not match_id:
logger.warning(f"Bet {bet_data['uuid']} has no match_id in details, skipping")
continue
bet = Bet( bet = Bet(
uuid=bet_data['uuid'], uuid=bet_data['uuid'],
sync_id=report_sync.id, sync_id=report_sync.id,
client_id=client_id, client_id=client_id,
fixture_id=bet_data['fixture_id'], fixture_id=bet_data['fixture_id'],
match_id=first_detail.get('match_id'), match_id=match_id,
match_number=first_detail.get('match_number'), match_number=match_number,
bet_datetime=bet_datetime, bet_datetime=bet_datetime,
paid=bet_data.get('paid', False), paid=bet_data.get('paid', False),
paid_out=bet_data.get('paid_out', False), paid_out=bet_data.get('paid_out', False),
...@@ -1248,10 +1264,10 @@ def api_reports_sync(): ...@@ -1248,10 +1264,10 @@ def api_reports_sync():
existing_stats.fixture_id = stats_data['fixture_id'] existing_stats.fixture_id = stats_data['fixture_id']
existing_stats.match_datetime = datetime.fromisoformat(stats_data['match_datetime']) existing_stats.match_datetime = datetime.fromisoformat(stats_data['match_datetime'])
existing_stats.total_bets = stats_data['total_bets'] existing_stats.total_bets = stats_data['total_bets']
existing_stats.total_amount_collected = stats_data['total_amount_collected'] existing_stats.total_amount_collected = stats_data.get('total_amount_collected', 0.0) or 0.0
existing_stats.total_redistributed = stats_data['total_redistributed'] existing_stats.total_redistributed = stats_data.get('total_redistributed', 0.0) or 0.0
existing_stats.actual_result = stats_data['actual_result'] existing_stats.actual_result = stats_data.get('actual_result')
existing_stats.extraction_result = stats_data['extraction_result'] existing_stats.extraction_result = stats_data.get('extraction_result')
existing_stats.cap_applied = stats_data.get('cap_applied', False) existing_stats.cap_applied = stats_data.get('cap_applied', False)
existing_stats.cap_percentage = stats_data.get('cap_percentage') existing_stats.cap_percentage = stats_data.get('cap_percentage')
existing_stats.accumulated_shortfall = stats_data.get('accumulated_shortfall', 0.00) existing_stats.accumulated_shortfall = stats_data.get('accumulated_shortfall', 0.00)
...@@ -1307,10 +1323,10 @@ def api_reports_sync(): ...@@ -1307,10 +1323,10 @@ def api_reports_sync():
fixture_id=stats_data['fixture_id'], fixture_id=stats_data['fixture_id'],
match_datetime=match_datetime, match_datetime=match_datetime,
total_bets=stats_data['total_bets'], total_bets=stats_data['total_bets'],
total_amount_collected=stats_data['total_amount_collected'], total_amount_collected=stats_data.get('total_amount_collected', 0.0) or 0.0,
total_redistributed=stats_data['total_redistributed'], total_redistributed=stats_data.get('total_redistributed', 0.0) or 0.0,
actual_result=stats_data['actual_result'], actual_result=stats_data.get('actual_result'),
extraction_result=stats_data['extraction_result'], extraction_result=stats_data.get('extraction_result'),
cap_applied=stats_data.get('cap_applied', False), cap_applied=stats_data.get('cap_applied', False),
cap_percentage=stats_data.get('cap_percentage'), cap_percentage=stats_data.get('cap_percentage'),
accumulated_shortfall=stats_data.get('accumulated_shortfall', 0.00), accumulated_shortfall=stats_data.get('accumulated_shortfall', 0.00),
...@@ -1363,11 +1379,43 @@ def api_reports_sync(): ...@@ -1363,11 +1379,43 @@ def api_reports_sync():
match_number = match_num match_number = match_num
# Calculate balance (payin - payout) # Calculate balance (payin - payout)
total_payin = stats_data['total_amount_collected'] total_payin = float(stats_data.get('total_amount_collected', 0.0) or 0.0)
total_payout = stats_data['total_redistributed'] total_payout = float(stats_data.get('total_redistributed', 0.0) or 0.0)
balance = total_payin - total_payout balance = total_payin - total_payout
# Create MatchReport record # Check if MatchReport already exists for this match and client
existing_match_report = MatchReport.query.filter_by(
client_id=client_id,
match_id=match_id
).first()
if existing_match_report:
# Update existing MatchReport with all fields
existing_match_report.sync_id = report_sync.id
existing_match_report.match_number = match_number
existing_match_report.fixture_id = stats_data['fixture_id']
existing_match_report.match_datetime = datetime.fromisoformat(stats_data['match_datetime'])
existing_match_report.total_bets = stats_data['total_bets']
existing_match_report.winning_bets = winning_bets
existing_match_report.losing_bets = losing_bets
existing_match_report.pending_bets = pending_bets
existing_match_report.total_payin = total_payin
existing_match_report.total_payout = total_payout
existing_match_report.balance = balance
existing_match_report.actual_result = stats_data.get('actual_result')
existing_match_report.extraction_result = stats_data.get('extraction_result')
existing_match_report.cap_applied = stats_data.get('cap_applied', False)
existing_match_report.cap_percentage = stats_data.get('cap_percentage')
existing_match_report.cap_compensation_balance = cap_compensation_balance
existing_match_report.accumulated_shortfall = stats_data.get('accumulated_shortfall', 0.00)
existing_match_report.under_bets = stats_data.get('under_bets', 0)
existing_match_report.under_amount = stats_data.get('under_amount', 0.00)
existing_match_report.over_bets = stats_data.get('over_bets', 0)
existing_match_report.over_amount = stats_data.get('over_amount', 0.00)
existing_match_report.result_breakdown = stats_data.get('result_breakdown')
logger.info(f"Updated existing MatchReport for match {match_id}")
else:
# Create new MatchReport record
match_report = MatchReport( match_report = MatchReport(
sync_id=report_sync.id, sync_id=report_sync.id,
client_id=client_id, client_id=client_id,
...@@ -1383,8 +1431,8 @@ def api_reports_sync(): ...@@ -1383,8 +1431,8 @@ def api_reports_sync():
total_payin=total_payin, total_payin=total_payin,
total_payout=total_payout, total_payout=total_payout,
balance=balance, balance=balance,
actual_result=stats_data['actual_result'], actual_result=stats_data.get('actual_result'),
extraction_result=stats_data['extraction_result'], extraction_result=stats_data.get('extraction_result'),
cap_applied=stats_data.get('cap_applied', False), cap_applied=stats_data.get('cap_applied', False),
cap_percentage=stats_data.get('cap_percentage'), cap_percentage=stats_data.get('cap_percentage'),
cap_compensation_balance=cap_compensation_balance, cap_compensation_balance=cap_compensation_balance,
...@@ -1396,6 +1444,8 @@ def api_reports_sync(): ...@@ -1396,6 +1444,8 @@ def api_reports_sync():
result_breakdown=stats_data.get('result_breakdown') result_breakdown=stats_data.get('result_breakdown')
) )
db.session.add(match_report) db.session.add(match_report)
logger.info(f"Created new MatchReport for match {match_id}")
match_reports_count += 1 match_reports_count += 1
# If extraction_stats is empty but we have bets, create MatchReport records from bets # If extraction_stats is empty but we have bets, create MatchReport records from bets
...@@ -1422,6 +1472,8 @@ def api_reports_sync(): ...@@ -1422,6 +1472,8 @@ def api_reports_sync():
).group_by(Bet.match_id, Bet.match_number, Bet.fixture_id).all() ).group_by(Bet.match_id, Bet.match_number, Bet.fixture_id).all()
for match_id, match_number, fixture_id, match_datetime, total_bets, total_payin in bets_by_match: for match_id, match_number, fixture_id, match_datetime, total_bets, total_payin in bets_by_match:
# Convert Decimal to float for calculations
total_payin = float(total_payin) if total_payin is not None else 0.0
# Calculate winning/losing/pending bets from bet details # Calculate winning/losing/pending bets from bet details
winning_bets = 0 winning_bets = 0
losing_bets = 0 losing_bets = 0
...@@ -1441,7 +1493,7 @@ def api_reports_sync(): ...@@ -1441,7 +1493,7 @@ def api_reports_sync():
for result, count, total_win in bet_details_query.all(): for result, count, total_win in bet_details_query.all():
if result == 'won': if result == 'won':
winning_bets = count winning_bets = count
total_payout += total_win or 0.0 total_payout += float(total_win) if total_win is not None else 0.0
elif result == 'lost': elif result == 'lost':
losing_bets = count losing_bets = count
elif result == 'pending': elif result == 'pending':
...@@ -1456,8 +1508,11 @@ def api_reports_sync(): ...@@ -1456,8 +1508,11 @@ def api_reports_sync():
).first() ).first()
if existing_match_report: if existing_match_report:
# Update existing MatchReport # Update existing MatchReport with all fields
existing_match_report.sync_id = report_sync.id existing_match_report.sync_id = report_sync.id
existing_match_report.match_number = match_number
existing_match_report.fixture_id = fixture_id
existing_match_report.match_datetime = match_datetime
existing_match_report.total_bets = total_bets existing_match_report.total_bets = total_bets
existing_match_report.winning_bets = winning_bets existing_match_report.winning_bets = winning_bets
existing_match_report.losing_bets = losing_bets existing_match_report.losing_bets = losing_bets
...@@ -1466,6 +1521,10 @@ def api_reports_sync(): ...@@ -1466,6 +1521,10 @@ def api_reports_sync():
existing_match_report.total_payout = total_payout existing_match_report.total_payout = total_payout
existing_match_report.balance = balance existing_match_report.balance = balance
existing_match_report.cap_compensation_balance = cap_compensation_balance existing_match_report.cap_compensation_balance = cap_compensation_balance
existing_match_report.under_bets = 0
existing_match_report.under_amount = 0.0
existing_match_report.over_bets = 0
existing_match_report.over_amount = 0.0
logger.info(f"Updated existing MatchReport for match {match_id}") logger.info(f"Updated existing MatchReport for match {match_id}")
else: else:
# Create new MatchReport # Create new MatchReport
...@@ -1484,6 +1543,8 @@ def api_reports_sync(): ...@@ -1484,6 +1543,8 @@ def api_reports_sync():
total_payin=total_payin, total_payin=total_payin,
total_payout=total_payout, total_payout=total_payout,
balance=balance, balance=balance,
actual_result=None,
extraction_result=None,
cap_compensation_balance=cap_compensation_balance cap_compensation_balance=cap_compensation_balance
) )
db.session.add(match_report) db.session.add(match_report)
......
...@@ -1121,6 +1121,116 @@ class Migration_014_AddAccumulatedShortfallAndCapPercentage(Migration): ...@@ -1121,6 +1121,116 @@ class Migration_014_AddAccumulatedShortfallAndCapPercentage(Migration):
def can_rollback(self) -> bool: def can_rollback(self) -> bool:
return True return True
class Migration_015_AllowNullResults(Migration):
"""Allow NULL values for actual_result and extraction_result columns in extraction_stats and match_reports tables for incomplete matches"""
def __init__(self):
super().__init__("015", "Allow NULL values for actual_result and extraction_result columns in extraction_stats and match_reports tables")
def up(self):
"""Allow NULL values for actual_result and extraction_result columns"""
try:
inspector = inspect(db.engine)
# Check extraction_stats columns
extraction_stats_columns = {col['name']: col['nullable'] for col in inspector.get_columns('extraction_stats')}
if 'actual_result' in extraction_stats_columns:
if extraction_stats_columns['actual_result'] is False:
with db.engine.connect() as conn:
conn.execute(text("""
ALTER TABLE extraction_stats
MODIFY COLUMN actual_result VARCHAR(50) NULL
"""))
conn.commit()
logger.info("Updated actual_result column to allow NULL in extraction_stats table")
else:
logger.info("actual_result column already allows NULL in extraction_stats table")
if 'extraction_result' in extraction_stats_columns:
if extraction_stats_columns['extraction_result'] is False:
with db.engine.connect() as conn:
conn.execute(text("""
ALTER TABLE extraction_stats
MODIFY COLUMN extraction_result VARCHAR(50) NULL
"""))
conn.commit()
logger.info("Updated extraction_result column to allow NULL in extraction_stats table")
else:
logger.info("extraction_result column already allows NULL in extraction_stats table")
# Check match_reports columns
match_reports_columns = {col['name']: col['nullable'] for col in inspector.get_columns('match_reports')}
if 'actual_result' in match_reports_columns:
if match_reports_columns['actual_result'] is False:
with db.engine.connect() as conn:
conn.execute(text("""
ALTER TABLE match_reports
MODIFY COLUMN actual_result VARCHAR(50) NULL
"""))
conn.commit()
logger.info("Updated actual_result column to allow NULL in match_reports table")
else:
logger.info("actual_result column already allows NULL in match_reports table")
if 'extraction_result' in match_reports_columns:
if match_reports_columns['extraction_result'] is False:
with db.engine.connect() as conn:
conn.execute(text("""
ALTER TABLE match_reports
MODIFY COLUMN extraction_result VARCHAR(50) NULL
"""))
conn.commit()
logger.info("Updated extraction_result column to allow NULL in match_reports table")
else:
logger.info("extraction_result column already allows NULL in match_reports table")
logger.info("Migration 015 completed successfully")
return True
except Exception as e:
logger.error(f"Migration 015 failed: {str(e)}")
raise
def down(self):
"""Revert actual_result and extraction_result columns to NOT NULL"""
try:
# Revert extraction_stats columns
with db.engine.connect() as conn:
conn.execute(text("""
ALTER TABLE extraction_stats
MODIFY COLUMN actual_result VARCHAR(50) NOT NULL
"""))
conn.execute(text("""
ALTER TABLE extraction_stats
MODIFY COLUMN extraction_result VARCHAR(50) NOT NULL
"""))
conn.commit()
# Revert match_reports columns
with db.engine.connect() as conn:
conn.execute(text("""
ALTER TABLE match_reports
MODIFY COLUMN actual_result VARCHAR(50) NOT NULL
"""))
conn.execute(text("""
ALTER TABLE match_reports
MODIFY COLUMN extraction_result VARCHAR(50) NOT NULL
"""))
conn.commit()
logger.info("Migration 015 rolled back successfully")
return True
except Exception as e:
logger.error(f"Rollback of migration 015 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"""
...@@ -1140,6 +1250,7 @@ class MigrationManager: ...@@ -1140,6 +1250,7 @@ class MigrationManager:
Migration_012_AddMatchNumberToBetsAndStats(), Migration_012_AddMatchNumberToBetsAndStats(),
Migration_013_CreateMatchReportsTable(), Migration_013_CreateMatchReportsTable(),
Migration_014_AddAccumulatedShortfallAndCapPercentage(), Migration_014_AddAccumulatedShortfallAndCapPercentage(),
Migration_015_AllowNullResults(),
] ]
def ensure_version_table(self): def ensure_version_table(self):
...@@ -1314,3 +1425,5 @@ def run_migrations(): ...@@ -1314,3 +1425,5 @@ def run_migrations():
def get_migration_status(): def get_migration_status():
"""Get migration status""" """Get migration status"""
return migration_manager.get_migration_status() return migration_manager.get_migration_status()
"""
Migration to allow NULL values for actual_result and extraction_result columns
in extraction_stats and match_reports tables for incomplete matches
"""
from app import db
def upgrade():
"""Allow NULL values for actual_result and extraction_result columns"""
try:
# Check if columns allow NULL in extraction_stats
inspector = db.inspect(db.engine)
extraction_stats_columns = {col['name']: col['nullable'] for col in inspector.get_columns('extraction_stats')}
if 'actual_result' in extraction_stats_columns:
if extraction_stats_columns['actual_result'] is False:
# Alter actual_result column to allow NULL
with db.engine.connect() as conn:
conn.execute(db.text("""
ALTER TABLE extraction_stats
MODIFY COLUMN actual_result VARCHAR(50) NULL
"""))
db.session.commit()
print("✓ Updated actual_result column to allow NULL in extraction_stats table")
else:
print("✓ actual_result column already allows NULL in extraction_stats table")
if 'extraction_result' in extraction_stats_columns:
if extraction_stats_columns['extraction_result'] is False:
# Alter extraction_result column to allow NULL
with db.engine.connect() as conn:
conn.execute(db.text("""
ALTER TABLE extraction_stats
MODIFY COLUMN extraction_result VARCHAR(50) NULL
"""))
db.session.commit()
print("✓ Updated extraction_result column to allow NULL in extraction_stats table")
else:
print("✓ extraction_result column already allows NULL in extraction_stats table")
# Check if columns allow NULL in match_reports
match_reports_columns = {col['name']: col['nullable'] for col in inspector.get_columns('match_reports')}
if 'actual_result' in match_reports_columns:
if match_reports_columns['actual_result'] is False:
# Alter actual_result column to allow NULL
with db.engine.connect() as conn:
conn.execute(db.text("""
ALTER TABLE match_reports
MODIFY COLUMN actual_result VARCHAR(50) NULL
"""))
db.session.commit()
print("✓ Updated actual_result column to allow NULL in match_reports table")
else:
print("✓ actual_result column already allows NULL in match_reports table")
if 'extraction_result' in match_reports_columns:
if match_reports_columns['extraction_result'] is False:
# Alter extraction_result column to allow NULL
with db.engine.connect() as conn:
conn.execute(db.text("""
ALTER TABLE match_reports
MODIFY COLUMN extraction_result VARCHAR(50) NULL
"""))
db.session.commit()
print("✓ Updated extraction_result column to allow NULL in match_reports table")
else:
print("✓ extraction_result column already allows NULL in match_reports table")
print("\n✓ Migration completed successfully!")
except Exception as e:
db.session.rollback()
print(f"✗ Error updating columns: {str(e)}")
raise
def downgrade():
"""Revert actual_result and extraction_result columns to NOT NULL"""
try:
# Revert extraction_stats columns
with db.engine.connect() as conn:
conn.execute(db.text("""
ALTER TABLE extraction_stats
MODIFY COLUMN actual_result VARCHAR(50) NOT NULL
"""))
db.session.commit()
print("✓ Reverted actual_result column to NOT NULL in extraction_stats table")
with db.engine.connect() as conn:
conn.execute(db.text("""
ALTER TABLE extraction_stats
MODIFY COLUMN extraction_result VARCHAR(50) NOT NULL
"""))
db.session.commit()
print("✓ Reverted extraction_result column to NOT NULL in extraction_stats table")
# Revert match_reports columns
with db.engine.connect() as conn:
conn.execute(db.text("""
ALTER TABLE match_reports
MODIFY COLUMN actual_result VARCHAR(50) NOT NULL
"""))
db.session.commit()
print("✓ Reverted actual_result column to NOT NULL in match_reports table")
with db.engine.connect() as conn:
conn.execute(db.text("""
ALTER TABLE match_reports
MODIFY COLUMN extraction_result VARCHAR(50) NOT NULL
"""))
db.session.commit()
print("✓ Reverted extraction_result column to NOT NULL in match_reports table")
print("\n✓ Downgrade completed successfully!")
except Exception as e:
db.session.rollback()
print(f"✗ Error reverting columns: {str(e)}")
raise
if __name__ == '__main__':
from app import create_app
app = create_app()
with app.app_context():
print("Running migration: Allow NULL values for actual_result and extraction_result")
print("=" * 70)
upgrade()
print("=" * 70)
...@@ -200,7 +200,7 @@ def matches(): ...@@ -200,7 +200,7 @@ def matches():
@login_required @login_required
@require_active_user @require_active_user
def fixture_detail(fixture_id): def fixture_detail(fixture_id):
"""Fixture detail page showing all matches in the fixture""" """Fixture detail page showing all matches in fixture"""
try: try:
from app.models import Match, FileUpload from app.models import Match, FileUpload
...@@ -224,7 +224,7 @@ def fixture_detail(fixture_id): ...@@ -224,7 +224,7 @@ def fixture_detail(fixture_id):
'created_by': matches[0].created_by 'created_by': matches[0].created_by
} }
# Get associated uploads for the fixture # Get associated uploads for fixture
match_ids = [m.id for m in matches] match_ids = [m.id for m in matches]
uploads = FileUpload.query.filter(FileUpload.match_id.in_(match_ids)).all() if match_ids else [] uploads = FileUpload.query.filter(FileUpload.match_id.in_(match_ids)).all() if match_ids else []
...@@ -337,7 +337,7 @@ def update_match_outcomes(match_id): ...@@ -337,7 +337,7 @@ def update_match_outcomes(match_id):
try: try:
from app.models import Match, MatchOutcome from app.models import Match, MatchOutcome
# Get the match # Get match
if current_user.is_admin: if current_user.is_admin:
match = Match.query.get_or_404(match_id) match = Match.query.get_or_404(match_id)
else: else:
...@@ -353,7 +353,7 @@ def update_match_outcomes(match_id): ...@@ -353,7 +353,7 @@ def update_match_outcomes(match_id):
# Update or create outcomes # Update or create outcomes
for column_name, float_value in outcomes_data.items(): for column_name, float_value in outcomes_data.items():
try: try:
# Validate the float value # Validate float value
float_val = float(float_value) float_val = float(float_value)
# Find existing outcome or create new one # Find existing outcome or create new one
...@@ -406,13 +406,13 @@ def delete_match_outcome(match_id, outcome_id): ...@@ -406,13 +406,13 @@ def delete_match_outcome(match_id, outcome_id):
try: try:
from app.models import Match, MatchOutcome from app.models import Match, MatchOutcome
# Get the match to verify ownership # Get match to verify ownership
if current_user.is_admin: if current_user.is_admin:
match = Match.query.get_or_404(match_id) match = Match.query.get_or_404(match_id)
else: else:
match = Match.query.filter_by(id=match_id, created_by=current_user.id).first_or_404() match = Match.query.filter_by(id=match_id, created_by=current_user.id).first_or_404()
# Get the outcome # Get outcome
outcome = MatchOutcome.query.filter_by( outcome = MatchOutcome.query.filter_by(
id=outcome_id, id=outcome_id,
match_id=match_id match_id=match_id
...@@ -1407,7 +1407,7 @@ def download_zip(match_id): ...@@ -1407,7 +1407,7 @@ def download_zip(match_id):
flash('ZIP file not found on disk', 'error') flash('ZIP file not found on disk', 'error')
abort(404) abort(404)
# Log the download # Log download
logger.info(f"ZIP file downloaded: {match.zip_filename} by user {current_user.username}") logger.info(f"ZIP file downloaded: {match.zip_filename} by user {current_user.username}")
return send_file(zip_path, as_attachment=True, download_name=match.zip_filename) return send_file(zip_path, as_attachment=True, download_name=match.zip_filename)
...@@ -1654,7 +1654,7 @@ def reports(): ...@@ -1654,7 +1654,7 @@ def reports():
if export_format: if export_format:
return export_reports(query, export_format) return export_reports(query, export_format)
# Aggregate data by client for the selected period using MatchReport # Aggregate data by client for selected period using MatchReport
# Build base query with filters # Build base query with filters
base_query = MatchReport.query base_query = MatchReport.query
...@@ -1717,7 +1717,7 @@ def reports(): ...@@ -1717,7 +1717,7 @@ def reports():
client_aggregates[client_id]['losing_bets'] += report.losing_bets client_aggregates[client_id]['losing_bets'] += report.losing_bets
client_aggregates[client_id]['pending_bets'] += report.pending_bets client_aggregates[client_id]['pending_bets'] += report.pending_bets
# Use the most recent CAP balance and accumulated shortfall for this client # Use most recent CAP balance and accumulated shortfall for this client
if report.match_datetime >= client_aggregates[client_id]['last_match_timestamp']: if report.match_datetime >= client_aggregates[client_id]['last_match_timestamp']:
client_aggregates[client_id]['cap_balance'] = float(report.cap_compensation_balance) if report.cap_compensation_balance else 0.0 client_aggregates[client_id]['cap_balance'] = float(report.cap_compensation_balance) if report.cap_compensation_balance else 0.0
client_aggregates[client_id]['accumulated_shortfall'] = float(report.accumulated_shortfall) if report.accumulated_shortfall else 0.0 client_aggregates[client_id]['accumulated_shortfall'] = float(report.accumulated_shortfall) if report.accumulated_shortfall else 0.0
...@@ -1760,7 +1760,6 @@ def reports(): ...@@ -1760,7 +1760,6 @@ def reports():
total_balance = total_payin - total_payout total_balance = total_payin - total_payout
cap_balance = clients_list[0]['cap_balance'] if clients_list else 0.0 cap_balance = clients_list[0]['cap_balance'] if clients_list else 0.0
accumulated_shortfall = clients_list[0]['accumulated_shortfall'] if clients_list else 0.0 accumulated_shortfall = clients_list[0]['accumulated_shortfall'] if clients_list else 0.0
accumulated_shortfall = clients_list[0]['accumulated_shortfall'] if clients_list else 0.0
# Pagination # Pagination
total_clients = len(clients_list) total_clients = len(clients_list)
...@@ -2129,7 +2128,7 @@ def sync_logs(): ...@@ -2129,7 +2128,7 @@ def sync_logs():
if end_date_filter: if end_date_filter:
try: try:
end_date = datetime.strptime(end_date_filter, '%Y-%m-%d') end_date = datetime.strptime(end_date_filter, '%Y-%m-%d')
# Include the entire end date # Include entire end date
end_date = end_date.replace(hour=23, minute=59, second=59) end_date = end_date.replace(hour=23, minute=59, second=59)
query = query.filter(ReportSyncLog.created_at <= end_date) query = query.filter(ReportSyncLog.created_at <= end_date)
except ValueError: except ValueError:
...@@ -2439,6 +2438,7 @@ def export_sync_logs(export_format): ...@@ -2439,6 +2438,7 @@ def export_sync_logs(export_format):
except Exception as e: except Exception as e:
logger.error(f"Export sync logs error: {str(e)}") logger.error(f"Export sync logs error: {str(e)}")
flash('Error exporting sync logs', 'error') flash('Error exporting sync logs', 'error')
return redirect(url_for('main.sync_logs'))
@csrf.exempt @csrf.exempt
@bp.route('/client-report/<client_id>') @bp.route('/client-report/<client_id>')
...@@ -2534,23 +2534,34 @@ def client_report_detail(client_id): ...@@ -2534,23 +2534,34 @@ def client_report_detail(client_id):
if end_date: if end_date:
query = query.filter(MatchReport.match_datetime <= end_date) query = query.filter(MatchReport.match_datetime <= end_date)
# Get all matching match reports for this client with pagination # Get all matching match reports for this client (without pagination for totals)
all_match_reports = query.all()
# Get paginated match reports for display
match_reports_pagination = query.order_by(MatchReport.match_datetime.desc()).paginate( match_reports_pagination = query.order_by(MatchReport.match_datetime.desc()).paginate(
page=page, per_page=per_page, error_out=False page=page, per_page=per_page, error_out=False
) )
match_reports = match_reports_pagination.items match_reports = match_reports_pagination.items
# Calculate totals from match reports # Calculate totals from ALL match reports (not just paginated)
total_payin = sum(float(r.total_payin) for r in match_reports if r.total_payin) total_payin = sum(float(r.total_payin) for r in all_match_reports if r.total_payin)
total_payout = sum(float(r.total_payout) for r in match_reports if r.total_payout) total_payout = sum(float(r.total_payout) for r in all_match_reports if r.total_payout)
total_balance = total_payin - total_payout total_balance = total_payin - total_payout
total_bets = sum(r.total_bets for r in match_reports) total_bets = sum(r.total_bets for r in all_match_reports)
total_matches = len(match_reports) total_matches = len(all_match_reports)
winning_bets = sum(r.winning_bets for r in match_reports) winning_bets = sum(r.winning_bets for r in all_match_reports)
losing_bets = sum(r.losing_bets for r in match_reports) losing_bets = sum(r.losing_bets for r in all_match_reports)
pending_bets = sum(r.pending_bets for r in match_reports) pending_bets = sum(r.pending_bets for r in all_match_reports)
cap_balance = float(match_reports[0].cap_compensation_balance) if match_reports and match_reports[0].cap_compensation_balance else 0.0
accumulated_shortfall = float(match_reports[0].accumulated_shortfall) if match_reports and match_reports[0].accumulated_shortfall else 0.0 # Use most recent CAP balance and accumulated shortfall from all reports
if all_match_reports:
# Sort by match_datetime descending to get most recent
sorted_reports = sorted(all_match_reports, key=lambda x: x.match_datetime or datetime.min, reverse=True)
cap_balance = float(sorted_reports[0].cap_compensation_balance) if sorted_reports[0].cap_compensation_balance else 0.0
accumulated_shortfall = float(sorted_reports[0].accumulated_shortfall) if sorted_reports[0].accumulated_shortfall else 0.0
else:
cap_balance = 0.0
accumulated_shortfall = 0.0
# Get client token name # Get client token name
client_activity = ClientActivity.query.filter_by(rustdesk_id=client_id).first() client_activity = ClientActivity.query.filter_by(rustdesk_id=client_id).first()
...@@ -2593,6 +2604,7 @@ def client_report_detail(client_id): ...@@ -2593,6 +2604,7 @@ def client_report_detail(client_id):
end_date=end_date_filter if 'end_date_filter' in locals() else '', end_date=end_date_filter if 'end_date_filter' in locals() else '',
start_time=start_time_filter if 'start_time_filter' in locals() else '', start_time=start_time_filter if 'start_time_filter' in locals() else '',
end_time=end_time_filter if 'end_time_filter' in locals() else '')) end_time=end_time_filter if 'end_time_filter' in locals() else ''))
@csrf.exempt @csrf.exempt
@bp.route('/client-report/<client_id>/match/<int:match_id>') @bp.route('/client-report/<client_id>/match/<int:match_id>')
@login_required @login_required
...@@ -2681,13 +2693,7 @@ def match_report_detail(client_id, match_id): ...@@ -2681,13 +2693,7 @@ def match_report_detail(client_id, match_id):
flash('Access denied to this client', 'error') flash('Access denied to this client', 'error')
return redirect(url_for('main.reports')) return redirect(url_for('main.reports'))
# Apply date filters # Get match report (don't apply date filters - we want specific match)
if start_date:
query = query.filter(MatchReport.match_datetime >= start_date)
if end_date:
query = query.filter(MatchReport.match_datetime <= end_date)
# Get the match report
match_report = query.first() match_report = query.first()
if not match_report: if not match_report:
...@@ -2731,6 +2737,8 @@ def match_report_detail(client_id, match_id): ...@@ -2731,6 +2737,8 @@ def match_report_detail(client_id, match_id):
except Exception as e: except Exception as e:
logger.error(f"Match report detail error: {str(e)}") logger.error(f"Match report detail error: {str(e)}")
flash('Error loading match report details', 'error') flash('Error loading match report details', 'error')
return redirect(url_for('main.match_report_detail', client_id=client_id, match_id=match_id))
@csrf.exempt @csrf.exempt
@bp.route('/client-report/<client_id>/match/<int:match_id>/bet/<bet_uuid>') @bp.route('/client-report/<client_id>/match/<int:match_id>/bet/<bet_uuid>')
@login_required @login_required
...@@ -2764,7 +2772,7 @@ def bet_detail(client_id, match_id, bet_uuid): ...@@ -2764,7 +2772,7 @@ def bet_detail(client_id, match_id, bet_uuid):
flash('Access denied to this client', 'error') flash('Access denied to this client', 'error')
return redirect(url_for('main.reports')) return redirect(url_for('main.reports'))
# Get the bet # Get bet
bet = Bet.query.filter_by(client_id=client_id, match_id=match_id, uuid=bet_uuid).first() bet = Bet.query.filter_by(client_id=client_id, match_id=match_id, uuid=bet_uuid).first()
if not bet: if not bet:
......
...@@ -980,8 +980,8 @@ class ExtractionStats(db.Model): ...@@ -980,8 +980,8 @@ class ExtractionStats(db.Model):
total_bets = db.Column(db.Integer, nullable=False) total_bets = db.Column(db.Integer, nullable=False)
total_amount_collected = db.Column(db.Numeric(15, 2), nullable=False) total_amount_collected = db.Column(db.Numeric(15, 2), nullable=False)
total_redistributed = db.Column(db.Numeric(15, 2), nullable=False) total_redistributed = db.Column(db.Numeric(15, 2), nullable=False)
actual_result = db.Column(db.String(50), nullable=False) actual_result = db.Column(db.String(50), nullable=True)
extraction_result = db.Column(db.String(50), nullable=False) extraction_result = db.Column(db.String(50), nullable=True)
cap_applied = db.Column(db.Boolean, default=False) cap_applied = db.Column(db.Boolean, default=False)
cap_percentage = db.Column(db.Numeric(5, 2)) cap_percentage = db.Column(db.Numeric(5, 2))
accumulated_shortfall = db.Column(db.Numeric(15, 2), default=0.00) accumulated_shortfall = db.Column(db.Numeric(15, 2), default=0.00)
...@@ -1126,8 +1126,8 @@ class MatchReport(db.Model): ...@@ -1126,8 +1126,8 @@ class MatchReport(db.Model):
balance = db.Column(db.Numeric(15, 2), default=0.00) balance = db.Column(db.Numeric(15, 2), default=0.00)
# Match result # Match result
actual_result = db.Column(db.String(50), nullable=False) actual_result = db.Column(db.String(50), nullable=True)
extraction_result = db.Column(db.String(50), nullable=False) extraction_result = db.Column(db.String(50), nullable=True)
# CAP information # CAP information
cap_applied = db.Column(db.Boolean, default=False) cap_applied = db.Column(db.Boolean, default=False)
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
background-color: rgba(255,255,255,0.1); background-color: rgba(255,255,255,0.1);
} }
.container { .container {
max-width: 1200px; max-width: 100%;
margin: 2rem auto; margin: 2rem auto;
padding: 0 2rem; padding: 0 2rem;
} }
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
padding: 2rem; padding: 2rem;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: 0 2px 10px rgba(0,0,0,0.1);
max-width: 100%;
} }
.btn { .btn {
padding: 8px 16px; padding: 8px 16px;
......
"""
Test script to verify that incremental reports sync updates existing MatchReport records
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from datetime import datetime, timedelta
import uuid
from app import create_app, db
from app.models import ReportSync, Bet, BetDetail, ExtractionStats, MatchReport, ClientActivity, APIToken, User
def create_test_data():
"""Create test data to verify the fix"""
app = create_app()
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['TESTING'] = True
with app.app_context():
db.create_all()
# Create test user
user = User(username='testuser', email='test@example.com', is_active=True, is_admin=True)
user.set_password('testpassword')
db.session.add(user)
db.session.commit()
# Create test API token
api_token = user.generate_api_token('Test Token')
db.session.commit()
# Create test client activity
client_activity = ClientActivity(
api_token_id=api_token.id,
rustdesk_id='test_client_001',
last_seen=datetime.utcnow()
)
db.session.add(client_activity)
db.session.commit()
# Create first sync (full sync)
sync1_data = {
'sync_id': f'sync_test_1_{uuid.uuid4()}',
'client_id': 'test_client_001',
'sync_timestamp': datetime.utcnow().isoformat(),
'date_range': 'all',
'start_date': (datetime.utcnow() - timedelta(days=7)).isoformat(),
'end_date': datetime.utcnow().isoformat(),
'bets': [
{
'uuid': str(uuid.uuid4()),
'fixture_id': 'fixture_test_001',
'bet_datetime': (datetime.utcnow() - timedelta(hours=2)).isoformat(),
'paid': False,
'paid_out': False,
'total_amount': 500.00,
'bet_count': 1,
'details': [
{
'match_id': 123,
'match_number': 1,
'outcome': 'WIN1',
'amount': 500.00,
'win_amount': 0.00,
'result': 'pending'
}
]
}
],
'extraction_stats': [
{
'match_id': 123,
'fixture_id': 'fixture_test_001',
'match_datetime': (datetime.utcnow() - timedelta(hours=1)).isoformat(),
'total_bets': 10,
'total_amount_collected': 2000.00,
'total_redistributed': 1500.00,
'actual_result': 'WIN1',
'extraction_result': 'WIN1',
'cap_applied': False,
'under_bets': 5,
'under_amount': 1000.00,
'over_bets': 5,
'over_amount': 1000.00,
'result_breakdown': {
'WIN1': {'bets': 3, 'amount': 600.00},
'X1': {'bets': 2, 'amount': 400.00},
'WIN2': {'bets': 5, 'amount': 1000.00}
}
}
],
'cap_compensation_balance': 0.00,
'summary': {
'total_payin': 2000.00,
'total_payout': 1500.00,
'net_profit': 500.00,
'total_bets': 10,
'total_matches': 1
}
}
return app, user, api_token, client_activity, sync1_data
def test_matchreport_update():
"""Test that existing MatchReport records are updated in incremental sync"""
print("=" * 80)
print("Testing MatchReport Update in Incremental Sync")
print("=" * 80)
app, user, api_token, client_activity, sync1_data = create_test_data()
with app.app_context():
# Process first sync (full sync)
print("\n1. Processing first sync (full sync)...")
# Create report sync record
sync1 = ReportSync(
sync_id=sync1_data['sync_id'],
client_id=sync1_data['client_id'],
sync_timestamp=datetime.fromisoformat(sync1_data['sync_timestamp']),
date_range=sync1_data['date_range'],
start_date=datetime.fromisoformat(sync1_data['start_date']),
end_date=datetime.fromisoformat(sync1_data['end_date']),
total_payin=sync1_data['summary']['total_payin'],
total_payout=sync1_data['summary']['total_payout'],
net_profit=sync1_data['summary']['net_profit'],
total_bets=sync1_data['summary']['total_bets'],
total_matches=sync1_data['summary']['total_matches'],
cap_compensation_balance=sync1_data['cap_compensation_balance']
)
db.session.add(sync1)
db.session.commit()
# Process bets
for bet_data in sync1_data['bets']:
bet = Bet(
uuid=bet_data['uuid'],
sync_id=sync1.id,
client_id=sync1_data['client_id'],
fixture_id=bet_data['fixture_id'],
match_id=bet_data['details'][0]['match_id'],
match_number=bet_data['details'][0]['match_number'],
bet_datetime=datetime.fromisoformat(bet_data['bet_datetime']),
paid=bet_data['paid'],
paid_out=bet_data['paid_out'],
total_amount=bet_data['total_amount'],
bet_count=bet_data['bet_count']
)
db.session.add(bet)
db.session.flush()
for detail_data in bet_data['details']:
bet_detail = BetDetail(
bet_id=bet.id,
match_id=detail_data['match_id'],
match_number=detail_data['match_number'],
outcome=detail_data['outcome'],
amount=detail_data['amount'],
win_amount=detail_data['win_amount'],
result=detail_data['result']
)
db.session.add(bet_detail)
# Process extraction stats and create MatchReport
for stats_data in sync1_data['extraction_stats']:
# Create extraction stats
extraction_stats = ExtractionStats(
sync_id=sync1.id,
client_id=sync1_data['client_id'],
match_id=stats_data['match_id'],
match_number=sync1_data['bets'][0]['details'][0]['match_number'],
fixture_id=stats_data['fixture_id'],
match_datetime=datetime.fromisoformat(stats_data['match_datetime']),
total_bets=stats_data['total_bets'],
total_amount_collected=stats_data['total_amount_collected'],
total_redistributed=stats_data['total_redistributed'],
actual_result=stats_data['actual_result'],
extraction_result=stats_data['extraction_result'],
cap_applied=stats_data['cap_applied'],
under_bets=stats_data['under_bets'],
under_amount=stats_data['under_amount'],
over_bets=stats_data['over_bets'],
over_amount=stats_data['over_amount'],
result_breakdown=stats_data['result_breakdown']
)
db.session.add(extraction_stats)
# Create MatchReport (this is what we fixed)
match_report = MatchReport(
sync_id=sync1.id,
client_id=sync1_data['client_id'],
client_token_name=api_token.name,
match_id=stats_data['match_id'],
match_number=sync1_data['bets'][0]['details'][0]['match_number'],
fixture_id=stats_data['fixture_id'],
match_datetime=datetime.fromisoformat(stats_data['match_datetime']),
total_bets=stats_data['total_bets'],
winning_bets=3,
losing_bets=5,
pending_bets=2,
total_payin=stats_data['total_amount_collected'],
total_payout=stats_data['total_redistributed'],
balance=stats_data['total_amount_collected'] - stats_data['total_redistributed'],
actual_result=stats_data['actual_result'],
extraction_result=stats_data['extraction_result'],
cap_applied=stats_data['cap_applied'],
cap_compensation_balance=sync1_data['cap_compensation_balance'],
under_bets=stats_data['under_bets'],
under_amount=stats_data['under_amount'],
over_bets=stats_data['over_bets'],
over_amount=stats_data['over_amount'],
result_breakdown=stats_data['result_breakdown']
)
db.session.add(match_report)
db.session.commit()
# Verify MatchReport was created
match_report_count = MatchReport.query.count()
print(f" MatchReport count after first sync: {match_report_count}")
assert match_report_count == 1, "Should have created 1 MatchReport"
original_match_report = MatchReport.query.first()
print(f" Original MatchReport sync_id: {original_match_report.sync_id}")
print(f" Original MatchReport total_bets: {original_match_report.total_bets}")
print(f" Original MatchReport winning_bets: {original_match_report.winning_bets}")
# Create incremental sync with updated data
print("\n2. Creating incremental sync with updated data...")
sync2_data = sync1_data.copy()
sync2_data['sync_id'] = f'sync_test_2_{uuid.uuid4()}'
sync2_data['sync_timestamp'] = (datetime.utcnow() + timedelta(minutes=10)).isoformat()
sync2_data['extraction_stats'][0]['total_bets'] = 15 # Updated value
sync2_data['extraction_stats'][0]['total_amount_collected'] = 2500.00 # Updated value
sync2_data['extraction_stats'][0]['total_redistributed'] = 1800.00 # Updated value
sync2_data['summary']['total_bets'] = 15
sync2_data['summary']['total_payin'] = 2500.00
sync2_data['summary']['total_payout'] = 1800.00
sync2_data['summary']['net_profit'] = 700.00
# Now simulate processing the incremental sync through the API endpoint
from app.api.routes import api_reports_sync
from flask import Flask, request, jsonify
# Create a test client
test_client = app.test_client()
# Set up headers with authentication
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_token.plain_token}'
}
# Send the sync request
response = test_client.post(
'/api/reports/sync',
json=sync2_data,
headers=headers
)
print(f" API Response status code: {response.status_code}")
assert response.status_code == 200, "Sync should succeed"
# Verify the response
response_json = response.get_json()
print(f" Response success: {response_json['success']}")
assert response_json['success'] == True, "Sync should be successful"
print(f" Synced count: {response_json['synced_count']}")
# Check MatchReport was updated, not duplicated
print("\n3. Verifying MatchReport was updated...")
match_report_count = MatchReport.query.count()
print(f" MatchReport count after incremental sync: {match_report_count}")
assert match_report_count == 1, "Should not create duplicate MatchReport"
updated_match_report = MatchReport.query.first()
print(f" Updated MatchReport sync_id: {updated_match_report.sync_id}")
print(f" Updated MatchReport total_bets: {updated_match_report.total_bets}")
assert updated_match_report.total_bets == 15, "Should update total_bets"
print(f" Updated MatchReport total_payin: {updated_match_report.total_payin}")
assert updated_match_report.total_payin == 2500.00, "Should update total_payin"
print(f" Updated MatchReport total_payout: {updated_match_report.total_payout}")
assert updated_match_report.total_payout == 1800.00, "Should update total_payout"
print("\n✓ SUCCESS: MatchReport updated correctly in incremental sync!")
# Cleanup
db.session.remove()
db.drop_all()
if __name__ == "__main__":
test_matchreport_update()
\ No newline at end of file
"""
Simple test to verify MatchReport update logic without database connection
This tests the core logic directly without requiring a running MySQL server
"""
from datetime import datetime, timedelta
import uuid
import sys
import os
# Add the project root to Python path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def test_matchreport_logic():
"""Test MatchReport update logic"""
print("=" * 80)
print("Testing MatchReport Update Logic")
print("=" * 80)
# Simulate what happens in a real sync
print("\n1. Simulating first sync (full sync)...")
# First, let's simulate existing match reports before incremental sync
existing_match_reports = [
{
'id': 1,
'client_id': 'test_client_001',
'match_id': 123,
'sync_id': 'sync_1',
'total_bets': 10,
'total_payin': 2000.00,
'total_payout': 1500.00,
'winning_bets': 3,
'losing_bets': 5,
'pending_bets': 2,
'cap_compensation_balance': 0.00
}
]
# Print existing state
print(f" Existing MatchReport count: {len(existing_match_reports)}")
for mr in existing_match_reports:
print(f" - Match {mr['match_id']}: {mr['total_bets']} bets, "
f"{mr['total_payin']:.2f} payin, {mr['total_payout']:.2f} payout")
# New sync data (incremental) with updated information
print("\n2. Processing incremental sync with updated data...")
sync_data = {
'sync_id': f'sync_2_{uuid.uuid4()}',
'client_id': 'test_client_001',
'sync_timestamp': (datetime.utcnow() + timedelta(minutes=10)).isoformat(),
'date_range': 'all',
'start_date': (datetime.utcnow() - timedelta(days=7)).isoformat(),
'end_date': datetime.utcnow().isoformat(),
'bets': [
{
'uuid': str(uuid.uuid4()),
'fixture_id': 'fixture_test_001',
'bet_datetime': (datetime.utcnow() - timedelta(hours=2)).isoformat(),
'paid': False,
'paid_out': False,
'total_amount': 600.00,
'bet_count': 1,
'details': [
{
'match_id': 123,
'match_number': 1,
'outcome': 'WIN1',
'amount': 600.00,
'win_amount': 0.00,
'result': 'pending'
}
]
}
],
'extraction_stats': [
{
'match_id': 123,
'fixture_id': 'fixture_test_001',
'match_datetime': (datetime.utcnow() - timedelta(hours=1)).isoformat(),
'total_bets': 15, # Updated
'total_amount_collected': 2500.00, # Updated
'total_redistributed': 1800.00, # Updated
'actual_result': 'WIN1',
'extraction_result': 'WIN1',
'cap_applied': False,
'under_bets': 7,
'under_amount': 1200.00,
'over_bets': 8,
'over_amount': 1300.00,
'result_breakdown': {
'WIN1': {'bets': 4, 'amount': 800.00},
'X1': {'bets': 3, 'amount': 600.00},
'WIN2': {'bets': 8, 'amount': 1100.00}
}
}
],
'cap_compensation_balance': 100.00,
'summary': {
'total_payin': 2500.00,
'total_payout': 1800.00,
'net_profit': 700.00,
'total_bets': 15,
'total_matches': 1
}
}
# Let's apply our fix logic
print("\n3. Applying the fix...")
updated_match_reports = []
for stats_data in sync_data['extraction_stats']:
match_id = stats_data['match_id']
client_id = sync_data['client_id']
# Check if MatchReport exists for this client and match
existing = None
for mr in existing_match_reports:
if mr['client_id'] == client_id and mr['match_id'] == match_id:
existing = mr
break
if existing:
print(f" Existing MatchReport found for match {match_id}")
print(f" Updating from {existing['sync_id']} to {sync_data['sync_id']}")
existing['sync_id'] = sync_data['sync_id']
existing['total_bets'] = stats_data['total_bets']
existing['total_payin'] = stats_data['total_amount_collected']
existing['total_payout'] = stats_data['total_redistributed']
existing['cap_compensation_balance'] = sync_data['cap_compensation_balance']
# Recalculate balance
existing['balance'] = existing['total_payin'] - existing['total_payout']
print(f" Updated: {existing['total_bets']} bets, "
f"{existing['total_payin']:.2f} payin, {existing['total_payout']:.2f} payout, "
f"balance: {existing['balance']:.2f}")
updated_match_reports.append(existing)
else:
print(f" Creating new MatchReport for match {match_id}")
new_mr = {
'id': len(existing_match_reports) + 1,
'client_id': client_id,
'match_id': match_id,
'sync_id': sync_data['sync_id'],
'total_bets': stats_data['total_bets'],
'total_payin': stats_data['total_amount_collected'],
'total_payout': stats_data['total_redistributed'],
'cap_compensation_balance': sync_data['cap_compensation_balance'],
'balance': stats_data['total_amount_collected'] - stats_data['total_redistributed']
}
updated_match_reports.append(new_mr)
# Verify the results
print("\n4. Verifying results...")
print(f" MatchReport count: {len(updated_match_reports)}")
assert len(updated_match_reports) == 1, "Should not create duplicate"
updated_report = updated_match_reports[0]
assert updated_report['sync_id'] == sync_data['sync_id'], "Sync ID should be updated"
assert updated_report['total_bets'] == 15, "Total bets should be updated"
assert updated_report['total_payin'] == 2500.00, "Total payin should be updated"
assert updated_report['total_payout'] == 1800.00, "Total payout should be updated"
assert updated_report['cap_compensation_balance'] == 100.00, "Cap compensation balance should be updated"
assert updated_report['balance'] == 700.00, "Balance should be recalculated"
print("\n✓ SUCCESS: MatchReport logic works correctly!")
print(f" - No duplicate MatchReport created")
print(f" - Existing record updated with new values")
print(f" - All fields properly modified")
if __name__ == "__main__":
test_matchreport_logic()
\ No newline at end of file
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