Add missing /api/cashier/start-games endpoint and complete betting system enhancements

- Add /api/cashier/start-games endpoint to routes.py that sends START_GAME message to message bus
- Complete betting system with win_amount calculation and paid_out tracking
- Update bet resolution logic for UNDER/OVER outcomes
- Add comprehensive result extraction with CAP logic
- Enhance match timer and games thread coordination
- Update verification pages to show proper win amounts and payout status
parent 5ed9a200
This diff is collapsed.
......@@ -43,6 +43,7 @@ class MatchTimerComponent(ThreadedComponent):
self.message_bus.subscribe(self.name, MessageType.START_GAME, self._handle_start_game)
self.message_bus.subscribe(self.name, MessageType.SCHEDULE_GAMES, self._handle_schedule_games)
self.message_bus.subscribe(self.name, MessageType.CUSTOM, self._handle_custom_message)
self.message_bus.subscribe(self.name, MessageType.NEXT_MATCH, self._handle_next_match)
logger.info("MatchTimer component initialized")
......@@ -111,6 +112,8 @@ class MatchTimerComponent(ThreadedComponent):
self._handle_schedule_games(message)
elif message.type == MessageType.CUSTOM:
self._handle_custom_message(message)
elif message.type == MessageType.NEXT_MATCH:
self._handle_next_match(message)
except Exception as e:
logger.error(f"Failed to process message: {e}")
......@@ -225,6 +228,30 @@ class MatchTimerComponent(ThreadedComponent):
except Exception as e:
logger.error(f"Failed to handle custom message: {e}")
def _handle_next_match(self, message: Message):
"""Handle NEXT_MATCH message - start the next match in sequence"""
try:
fixture_id = message.data.get("fixture_id")
match_id = message.data.get("match_id")
logger.info(f"Received NEXT_MATCH message for fixture {fixture_id}, match {match_id}")
# Find and start the next match
match_info = self._find_and_start_next_match()
if match_info:
logger.info(f"Started next match {match_info['match_id']} in fixture {match_info['fixture_id']}")
# Reset timer for next interval
match_interval = self._get_match_interval()
self._start_timer(match_interval * 60, match_info['fixture_id'])
else:
logger.info("No more matches to start, stopping timer")
self._stop_timer()
except Exception as e:
logger.error(f"Failed to handle NEXT_MATCH message: {e}")
def _start_timer(self, duration_seconds: int, fixture_id: Optional[str]):
"""Start the countdown timer"""
with self._timer_lock:
......
......@@ -68,6 +68,9 @@ class MessageType(Enum):
MATCH_START = "MATCH_START"
PLAY_VIDEO_MATCH = "PLAY_VIDEO_MATCH"
PLAY_VIDEO_MATCH_DONE = "PLAY_VIDEO_MATCH_DONE"
PLAY_VIDEO_RESULT = "PLAY_VIDEO_RESULT"
MATCH_DONE = "MATCH_DONE"
NEXT_MATCH = "NEXT_MATCH"
GAME_STATUS = "GAME_STATUS"
GAME_UPDATE = "GAME_UPDATE"
......@@ -645,4 +648,41 @@ class MessageBuilder:
"video_filename": video_filename,
"fixture_id": fixture_id
}
)
@staticmethod
def play_video_result(sender: str, fixture_id: str, match_id: int, result: str) -> Message:
"""Create PLAY_VIDEO_RESULT message"""
return Message(
type=MessageType.PLAY_VIDEO_RESULT,
sender=sender,
data={
"fixture_id": fixture_id,
"match_id": match_id,
"result": result
}
)
@staticmethod
def match_done(sender: str, fixture_id: str, match_id: int) -> Message:
"""Create MATCH_DONE message"""
return Message(
type=MessageType.MATCH_DONE,
sender=sender,
data={
"fixture_id": fixture_id,
"match_id": match_id
}
)
@staticmethod
def next_match(sender: str, fixture_id: str, match_id: int) -> Message:
"""Create NEXT_MATCH message"""
return Message(
type=MessageType.NEXT_MATCH,
sender=sender,
data={
"fixture_id": fixture_id,
"match_id": match_id
}
)
\ No newline at end of file
......@@ -681,6 +681,7 @@ class BetModel(BaseModel):
fixture_id = Column(String(255), nullable=False, comment='Reference to fixture_id from matches table')
bet_datetime = Column(DateTime, default=datetime.utcnow, nullable=False, comment='Bet creation timestamp')
paid = Column(Boolean, default=False, nullable=False, comment='Payment status (True if payment received)')
paid_out = Column(Boolean, default=False, nullable=False, comment='Payout status (True if winnings paid out)')
# Relationships
bet_details = relationship('BetDetailModel', back_populates='bet', cascade='all, delete-orphan')
......@@ -699,7 +700,38 @@ class BetModel(BaseModel):
def calculate_total_winnings(self) -> float:
"""Calculate total winnings from won bets"""
return sum(detail.amount for detail in self.bet_details if detail.result == 'win')
return sum(detail.win_amount for detail in self.bet_details if detail.result == 'win')
def get_overall_status(self) -> str:
"""Get overall bet status based on bet details"""
if not self.bet_details:
return 'pending'
results = [detail.result for detail in self.bet_details]
# If any detail is pending, bet is pending
if 'pending' in results:
return 'pending'
# If all results are cancelled, bet is cancelled
if all(result == 'cancelled' for result in results):
return 'cancelled'
# If any result is win, bet is win
if 'win' in results:
return 'win'
# Otherwise, all results are lost
return 'lost'
def is_paid_out(self) -> bool:
"""Check if bet winnings have been paid out"""
return self.paid_out
def mark_paid_out(self):
"""Mark bet as paid out"""
self.paid_out = True
self.updated_at = datetime.utcnow()
def to_dict(self, exclude_fields: Optional[List[str]] = None) -> Dict[str, Any]:
"""Convert to dictionary with bet details"""
......@@ -708,6 +740,8 @@ class BetModel(BaseModel):
result['total_amount'] = self.get_total_amount()
result['bet_count'] = self.get_bet_count()
result['has_pending'] = self.has_pending_bets()
result['overall_status'] = self.get_overall_status()
result['total_winnings'] = self.calculate_total_winnings()
return result
def __repr__(self):
......@@ -729,6 +763,7 @@ class BetDetailModel(BaseModel):
match_id = Column(Integer, ForeignKey('matches.id'), nullable=False, comment='Foreign key to matches table')
outcome = Column(String(255), nullable=False, comment='Bet outcome/prediction')
amount = Column(Float(precision=2), nullable=False, comment='Bet amount with 2 decimal precision')
win_amount = Column(Float(precision=2), default=0.0, nullable=False, comment='Winning amount (calculated when result is win)')
result = Column(Enum('win', 'lost', 'pending', 'cancelled'), default='pending', nullable=False, comment='Bet result status')
# Relationships
......@@ -751,12 +786,14 @@ class BetDetailModel(BaseModel):
"""Check if bet detail was cancelled"""
return self.result == 'cancelled'
def set_result(self, result: str):
"""Set bet result"""
def set_result(self, result: str, win_amount: float = None):
"""Set bet result and optionally win amount"""
valid_results = ['win', 'lost', 'pending', 'cancelled']
if result not in valid_results:
raise ValueError(f"Invalid result: {result}. Must be one of {valid_results}")
self.result = result
if win_amount is not None:
self.win_amount = win_amount
self.updated_at = datetime.utcnow()
def __repr__(self):
......
This diff is collapsed.
......@@ -4649,6 +4649,52 @@ def mark_admin_bet_paid(bet_id):
except Exception as e:
logger.error(f"API mark admin bet paid error: {e}")
@api_bp.route('/cashier/start-games', methods=['POST'])
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def start_games():
"""Start games for the first fixture - send START_GAME message to message bus"""
try:
from ..core.message_bus import MessageBuilder, MessageType
# Send START_GAME message to the message bus
# The games thread will handle finding and activating the appropriate matches
start_game_message = MessageBuilder.start_game(
sender="web_dashboard",
fixture_id=None # Let the games thread find the first fixture with pending matches
)
# Publish the message to the message bus
if api_bp.message_bus:
success = api_bp.message_bus.publish(start_game_message)
if success:
logger.info("START_GAME message sent to message bus")
return jsonify({
"success": True,
"message": "Start games request sent to games thread",
"fixture_id": None # Will be determined by games thread
})
else:
logger.error("Failed to publish START_GAME message to message bus")
return jsonify({
"success": False,
"error": "Failed to send start games request"
}), 500
else:
logger.error("Message bus not available")
return jsonify({
"success": False,
"error": "Message bus not available"
}), 500
except Exception as e:
logger.error(f"API start games error: {e}")
return jsonify({"error": str(e)}), 500
# Barcode Settings API routes
@api_bp.route('/barcode-settings')
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
......
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