def __refund_entry(self, entry): """ refund a single entry. THIS DOES NOT remove the entry from a contest! :param entry: :return: """ buyin = entry.contest_pool.prize_structure.buyin bm = BuyinManager(entry.user) transaction = None # Create a cash or ticket deposit as a refund, # based on what the user used to get into the contest if bm.entry_did_use_ticket(entry): tm = TicketManager(entry.user) tm.deposit(buyin) transaction = tm.transaction refund = self.__create_refund(transaction, entry) else: ct = CashTransaction(entry.user) ct.deposit(buyin) transaction = ct.transaction refund = self.__create_refund(transaction, entry) # Create refund transaction from escrow escrow_ct = CashTransaction(self.get_escrow_user()) escrow_ct.withdraw(buyin, trans=transaction) return refund
def create_withdrawal(user, amount, balance_result): test = CashTransaction(user) test.withdraw(amount) tran_det= CashTransactionDetail.objects.get( user=test.transaction_detail.user, transaction=test.transaction_detail.transaction) self.assertAlmostEquals(tran_det.amount, -amount) bal = CashBalance.objects.get( user=test.transaction_detail.user) self.assertAlmostEquals(bal.amount,balance_result)
def withdraw(self, amount): """ This method creates a cash withdrawal transaction and the appropriate book keeping for allowing :param amount: :return: """ # The first time withdraw is called, withdraw_object and withdraw_object.status will both not be set. # On a successful withdraw, withdraw_object and withdraw_object.status will both be set. # On an invalid withdraw (amount 0, amount too large, etc), # withdraw_object will be set but withdraw_object.status will not. # If withdraw_object is set, we want to raise a WithdrawCalledTwice exception if withdraw_object.status is set # or if it does not exist. if self.withdraw_object: try: if self.withdraw_object.status: raise WithdrawCalledTwiceException( type(self).__name__, 'withdraw() can only be called on the object one time') except: raise WithdrawCalledTwiceException( type(self).__name__, 'withdraw() can only be called on the object one time') amount = Decimal(amount) # # if this instance wasnt created by pk, its not set/created yet. if self.withdraw_object == None: logger.info('creating withdraw_object in parent') self.withdraw_object = self.withdraw_class() tm = TaxManager(self.user) self.withdraw_object.net_profit = tm.calendar_year_net_profit() # # throws exceptions if insufficient funds, or tax info required logger.info('parent - call validate_withdraw') self.validate_withdraw(amount) # # Performs the transaction since everything has been validated # and saves the the transaction detail with the withdraw object. logger.info('parent - create the cash transaction') ct = CashTransaction(self.user) ct.withdraw(amount) self.withdraw_object.cash_transaction_detail = ct.transaction_detail self.withdraw_object.status = self.get_withdraw_status( WithdrawStatusConstants.Pending.value) self.withdraw_object.save() # save in db
def __update_accounts(self, place, contest, entry, amount): """ Updates the accounts for Payout, FPP, Bonus, and Rake This gets run on each contest entry once the contest is finished. It: 1. Withdraws the payout amount from escrow's account. 2. Deposits that amount into the entry owner's account. 3. Pays out any Frequent Player Points earned. 4. :param place: :param contest: :param entry: :param amount: :return: """ payout = Payout() payout.rank = place payout.contest = contest payout.entry = entry tm = CashTransaction(entry.lineup.user) tm.deposit(amount) # # Take cash out of escrow ct = CashTransaction(self.get_escrow_user()) ct.withdraw(amount, tm.transaction) payout.transaction = tm.transaction payout.save() user = payout.entry.lineup.user rake_paid = contest.buyin * .10 # # Pays out FPP lsm = LoyaltyStatusManager(user) fppt = FppTransaction(user) # rake * base loyalty multiplier * the multiplier fppt.deposit((rake_paid * lsm.BASE_LOYALTY_MULTIPLIER) * lsm.get_fpp_multiplier(), trans=ct.transaction) fpp = FPP() fpp.contest = contest fpp.transaction = ct.transaction fpp.save() # # convert the bonus_cash for the user self.__convert_bonus_cash(user, rake_paid, payout.transaction) # # Create a rake transaction for the user rpt = RakepaidTransaction(user) rpt.deposit(rake_paid, trans=payout.transaction) msg = "User[" + payout.entry.lineup.user.username + "] was ranked #" + str( payout.rank) + " for contest #" + str( payout.contest.pk) + " and was paid out." Logger.log(ErrorCodes.INFO, "Contest Payout", msg)
def __payout_contest(self, contest): """ Method assumes the contest has never been paid out and is ready to be paid out. This is why the method is private and should be called from a separate method that does the individual error checking. """ try: c = ClosedContest.objects.get(pk=contest.pk) # print('%s already closed & paid out.'%str(contest)) return # go no further except: pass # # get the prize pool ranks for the contest ranks = Rank.objects.filter( prize_structure=contest.prize_structure).order_by('rank') # # get the entries for the contest entries = Entry.objects.filter(contest=contest) entries = entries.order_by('-lineup__fantasy_points') # # Validate the ranks are setup properly for rank in ranks: # # verify the abstract amount is correct type if not isinstance(rank.amount, AbstractAmount): raise mysite.exceptions.IncorrectVariableTypeException( type(self).__name__, 'rank') # # Get the transaction class and verify that it can deposit transaction_class = rank.amount.get_transaction_class() if not issubclass(transaction_class, CanDeposit): raise mysite.exceptions.IncorrectVariableTypeException( type(self).__name__, 'transaction_class') # print('------- entries [%s] contest [%s] -------' %(str(len(entries)), str(contest))) # # perform the payouts by going through each entry and finding # ties and ranks for the ties to chop. # print('======= ranks [%s] =======' % (str(ranks))) # # we now need to check which is shorter: the list of ranks or the list of entries, # and only use that many ranks for calculating winners! (its possible for # fewer entries than the total number of ranks! if len(entries) < len(ranks): logger.info('SUPERLAY PAYOUT CONTEST: %s' % contest) i = 0 while i < len(ranks[:len(entries)]): # print('++++ i (rank): %s +++++' % str(i) ) entries_to_pay = list() ranks_to_pay = list() entries_to_pay.append(entries[i]) ranks_to_pay.append(ranks[i]) score = entries[i].lineup.fantasy_points # # For each tie add the user to the list to chop the payment # and add the next payout to be split with the ties. while i + 1 < len(entries) and score == entries[ i + 1].lineup.fantasy_points: i += 1 entries_to_pay.append(entries[i]) if len(ranks) > i: ranks_to_pay.append(ranks[i]) self.__payout_spot(ranks_to_pay, entries_to_pay, contest) i += 1 # ############################################################### # rank all of the entries with the same payout algorithm. ############################################################### # j = 0 # last_fantasy_points = None # for entry in entries: # if last_fantasy_points is None: # last_fantasy_points = entry.lineup.fantasy_points # count_at_rank = Entry.objects.filter(contest=contest, lineup__fantasy_points=) # using the fantasy_points as the key, add/increment the entry id to the list. # the length of that list will be the # of entries at that rank, and # the rank will be the order of the keys. entry_fantasy_points_map = {} for entry in entries: try: entry_fantasy_points_map[entry.lineup.fantasy_points] += [ entry.pk ] except KeyError: entry_fantasy_points_map[entry.lineup.fantasy_points] = [ entry.pk ] # sort the fantasy points map on the map key (ascending) sorted_list = sorted(entry_fantasy_points_map.items(), key=lambda x: x[0]) # so its descending ie: [(75.5, [432, 213]), (50.25, [431234, 234534]), (25.0, [1, 123])] sorted_list.reverse() entry_rank = 1 for fantasy_points, entry_id_list in sorted_list: count_at_rank = len(entry_id_list) Entry.objects.filter(pk__in=entry_id_list).update( final_rank=entry_rank) entry_rank += count_at_rank # Determine what our net rake amount was. rake_transaction = None rake_post_overlay = calculate_rake(contest) # We made money on rake! No overlay, yaaay if rake_post_overlay > 0: # # Take cash out of escrow and deposit it into draftboard logger.info(('We made money on this contest, creating a Rake ' 'transaction for $%s. contest: %s') % (rake_post_overlay, contest)) escrow_withdraw_trans = CashTransaction(self.get_escrow_user()) escrow_withdraw_trans.withdraw(rake_post_overlay) draftboard_deposit_trans = CashTransaction( self.get_draftboard_user()) draftboard_deposit_trans.deposit( rake_post_overlay, trans=escrow_withdraw_trans.transaction) rake_transaction = escrow_withdraw_trans.transaction # We lost money on rake. :( elif rake_post_overlay < 0: # # Take cash out of draftboard and deposit it into escrow logger.info(('We lost money on this contest, creating a Rake ' 'transaction for $%s. contest: %s') % (rake_post_overlay, contest)) rake_post_overlay = abs(rake_post_overlay) draftboard_withdraw_trans = CashTransaction( self.get_draftboard_user()) draftboard_withdraw_trans.withdraw(rake_post_overlay) escrow_deposit_trans = CashTransaction(self.get_escrow_user()) escrow_deposit_trans.deposit( rake_post_overlay, trans=draftboard_withdraw_trans.transaction) rake_transaction = draftboard_withdraw_trans.transaction # We broke even on this contest, don't create a rake transaction below. elif rake_post_overlay == 0: logger.info( 'No rake was collected, not creating a Rake transaction. contest: %s' % contest) if rake_transaction: # links the contest with the rake payout rake = Rake() rake.contest = contest rake.transaction = rake_transaction rake.save() contest.status = Contest.CLOSED contest.save()
def buyin(self, contest_pool, lineup=None): """ Creates the buyin for the user based on the ContestPool and lineup. Lineup can be null or not passed to allow for reserving contest spots. This should be only run in a task :param contest_pool: the ContestPool to register in :param lineup: assumed the lineup is validated on creation :raises :class:`contest.exceptions.ContestCouldNotEnterException`: When the there is a race condition and the contest cannot be entered after max_retries :raises :class:`contest.exceptions.ContestIsNotAcceptingLineupsException`: When contest_pool does not have a draftgroup, because it is not accepting teams yet. :raises :class:`contest.exceptions.ContestLineupMismatchedDraftGroupsException`: When the lineup was picked from a draftgroup that does not match the contest_pool. :raises :class:`contest.exceptions.ContestIsInProgressOrClosedException`: When The contest_pool has started, or its past the start time :raises :class:`contest.exceptions.LineupDoesNotMatchUser`: When the lineup is not owned by the user. :raises :class:`contest.exceptions.ContestMaxEntriesReachedException`: When the max entries is reached by the lineup. """ # validate the contest and the lineup are allowed to be created self.lineup_contest(contest_pool, lineup) # Create either the ticket or cash transaction # Try to pay with a ticket first, if that doesn't work because the user doesn't have any # tickets, then try to pay with their cash balance. tm = TicketManager(self.user) # Get the transaction type - `ContestBuyin` # category = TransactionType.objects.get(pk=TransactionTypeConstants.ContestBuyin) try: tm.consume(amount=contest_pool.prize_structure.buyin) # keep a reference of the transaction. transaction = tm.transaction except (UserDoesNotHaveTicketException, ticket.models.TicketAmount.DoesNotExist): # Paying via Ticket failed, Create a cash transaciton with the type of 'ContestBuyin'. ct = CashTransaction(user=self.user) # Make the transaction a withdrawal. ct.withdraw(amount=contest_pool.prize_structure.buyin) # keep a reference of the transaction for user later. transaction = ct.transaction # # Transfer money into escrow escrow_ct = CashTransaction(self.get_escrow_user()) escrow_ct.deposit(contest_pool.prize_structure.buyin, trans=transaction) # # Create the Entry entry = Entry() entry.contest_pool = contest_pool # entry.contest = contest # the contest will be set later when the ContestPool starts entry.contest = None entry.lineup = lineup entry.user = self.user entry.save() # # Create the Buyin model buyin = Buyin() buyin.contest_pool = contest_pool buyin.transaction = transaction # buyin.contest = contest # the contest will be set later when the ContestPool starts (?) buyin.contest = None buyin.entry = entry buyin.save() # # Increment the contest_entry variable contest_pool.current_entries = F('current_entries') + 1 contest_pool.save() contest_pool.refresh_from_db() msg = "User[" + self.user.username + "] bought into the contest_pool #" \ + str(contest_pool.pk) + " with entry #" + str(entry.pk) Logger.log(ErrorCodes.INFO, "ContestPool Buyin", msg) # # pusher contest updates because entries were changed ContestPoolPush(ContestPoolSerializer(contest_pool).data).send() return entry