mirror of
https://github.com/kennethreitz-archive/www.gittip.com.git
synced 2026-06-21 15:50:59 +00:00
293 lines
11 KiB
HTML
293 lines
11 KiB
HTML
from decimal import Decimal
|
|
|
|
from aspen import Response, log
|
|
from aspen.utils import to_age
|
|
from gittip import db, AMOUNTS
|
|
from gittip.utils import get_participant
|
|
|
|
|
|
class Paydays(object):
|
|
|
|
def __init__(self, username, balance):
|
|
self._username = username
|
|
self._paydays = list(db.fetchall("SELECT ts_start, ts_end FROM paydays "
|
|
"ORDER BY ts_end DESC"))
|
|
self._npaydays = len(self._paydays)
|
|
self._exchanges = list(db.fetchall( "SELECT * FROM exchanges "
|
|
"WHERE participant=%s "
|
|
"ORDER BY timestamp ASC"
|
|
, (username,)
|
|
))
|
|
self._transfers = list(db.fetchall( "SELECT * FROM transfers "
|
|
"WHERE tipper=%s OR tippee=%s "
|
|
"ORDER BY timestamp ASC"
|
|
, (username, username)
|
|
))
|
|
self._balance = balance
|
|
|
|
|
|
def __iter__(self):
|
|
"""Yield iterators of events.
|
|
|
|
Each payday is expected to encompass 0 or 1 exchanges and 0 or more
|
|
transfers per participant. Here we knit them together along with start
|
|
and end events for each payday. If we have exchanges or transfers that
|
|
fall outside of a payday, then we have a logic bug. I am 50% confident
|
|
that this will manifest some day.
|
|
|
|
"""
|
|
_i = 1
|
|
for payday in self._paydays:
|
|
|
|
if not (self._exchanges or self._transfers):
|
|
# Show all paydays since the user started really participating.
|
|
break
|
|
|
|
payday_start = { 'event': 'payday-start'
|
|
, 'timestamp': payday['ts_start']
|
|
, 'number': self._npaydays - _i
|
|
, 'balance': Decimal('0.00')
|
|
}
|
|
payday_end = { 'event': 'payday-end'
|
|
, 'timestamp': payday['ts_end']
|
|
, 'number': self._npaydays - _i
|
|
}
|
|
received = { 'event': 'received'
|
|
, 'amount': Decimal('0.00')
|
|
, 'n': 0
|
|
}
|
|
_i += 1
|
|
|
|
|
|
events = []
|
|
while (self._exchanges or self._transfers):
|
|
|
|
# Take the next event, either an exchange or transfer.
|
|
# ====================================================
|
|
# We do this by peeking at both lists, and popping the list
|
|
# that has the next event.
|
|
|
|
exchange = self._exchanges[-1] if self._exchanges else None
|
|
transfer = self._transfers[-1] if self._transfers else None
|
|
|
|
if exchange is None:
|
|
event = self._transfers.pop()
|
|
elif transfer is None:
|
|
event = self._exchanges.pop()
|
|
elif transfer['timestamp'] > exchange['timestamp']:
|
|
event = self._transfers.pop()
|
|
else:
|
|
event = self._exchanges.pop()
|
|
|
|
if 'fee' in event:
|
|
if event['amount'] > 0:
|
|
event['event'] = 'charge'
|
|
else:
|
|
event['event'] = 'credit'
|
|
else:
|
|
event['event'] = 'transfer'
|
|
|
|
|
|
# Record the next event.
|
|
# ======================
|
|
|
|
if event['timestamp'] < payday_start['timestamp']:
|
|
if event['event'] == 'exchange':
|
|
back_on = self._exchanges
|
|
else:
|
|
back_on = self._transfers
|
|
back_on.append(event)
|
|
break
|
|
|
|
if event['event'] == 'transfer':
|
|
if event['tippee'] == self._username:
|
|
|
|
# Don't leak details about who tipped you. Only show
|
|
# aggregates for that.
|
|
|
|
received['amount'] += event['amount']
|
|
received['n'] += 1
|
|
|
|
#continue # Don't leak!
|
|
|
|
events.append(event)
|
|
|
|
if not events:
|
|
continue
|
|
|
|
|
|
# Calculate balance.
|
|
# ==================
|
|
|
|
prev = events[0]
|
|
prev['balance'] = self._balance
|
|
for event in events[1:] + [payday_start]:
|
|
if prev['event'] == 'charge':
|
|
balance = prev['balance'] - prev['amount']
|
|
elif prev['event'] == 'credit':
|
|
balance = prev['balance'] - prev['amount'] + prev['fee']
|
|
elif prev['event'] == 'transfer':
|
|
if prev['tippee'] == self._username:
|
|
balance = prev['balance'] - prev['amount']
|
|
else:
|
|
balance = prev['balance'] + prev['amount']
|
|
event['balance'] = balance
|
|
prev = event
|
|
self._balance = payday_start['balance']
|
|
|
|
yield payday_start
|
|
for event in reversed(events):
|
|
yield event
|
|
yield payday_end
|
|
|
|
# This should catch that logic bug.
|
|
if self._exchanges or self._transfers:
|
|
log("These should be empty:", self._exchanges, self._transfers)
|
|
raise "Logic bug in payday timestamping."
|
|
|
|
# ========================================================================== ^L
|
|
|
|
participant = get_participant(request, restrict=True)
|
|
paydays = Paydays(participant.username, participant.balance)
|
|
hero = "History"
|
|
title = "%s - %s" % (participant.username, hero)
|
|
locked = False
|
|
|
|
# ========================================================================== ^L
|
|
{% extends templates/profile.html %}
|
|
{% block page %}
|
|
|
|
<table id="history" class="centered">
|
|
<tr><td colspan="7">
|
|
<h2>{{ participant.username == user.username and "You" or participant.username }} joined
|
|
{{ to_age(participant.claimed_time) }}.</h2>
|
|
</td></tr>
|
|
<tr><td colspan="7">
|
|
|
|
<h2>{{ participant.username == user.username and "Your" or "Their" }} balance is
|
|
${{ participant.balance }}.</h2>
|
|
{% if user.ADMIN %}
|
|
<h2>Record an Exchange.</h2>
|
|
<form style="text-align: left;" action="record-an-exchange"
|
|
method="POST">
|
|
<input name="csrf_token" type="hidden" value="{{ csrf_token }}" />
|
|
<input name="amount" placeholder="amount" />
|
|
<input name="fee" placeholder="fee" />
|
|
<br />
|
|
<input name="note" placeholder="note" style="width: 230px" />
|
|
<button type="submit">Submit</button>
|
|
</form>
|
|
{% end %}
|
|
</td></tr>
|
|
{% for event in paydays %}
|
|
{% if event['event'] == 'payday-start' %}
|
|
<tr>
|
|
<th colspan="7"><h2>Gittip #{{ event['number'] }}
|
|
—{{ event['timestamp'].strftime("%B %d, %Y").replace(' 0', ' ') }}
|
|
({{ to_age(event['timestamp']) }})</h2></th>
|
|
</tr>
|
|
<tr class="head">
|
|
<td colspan="3" class="outside">← Outside</td>
|
|
<td colspan="4" class="inside">Inside Gittip →</td>
|
|
</tr>
|
|
<tr class="head">
|
|
<td class="bank">Bank</td>
|
|
<td class="card">Card</td>
|
|
<td class="fees">Fees</td>
|
|
<td class="credits">Credits</td>
|
|
<td class="debits">Debits</td>
|
|
<td class="balance">Balance</td>
|
|
<td class="notes">Notes</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="bank"></td>
|
|
<td class="card"></td>
|
|
<td class="fees"></td>
|
|
<td class="credits"></td>
|
|
<td class="debits"></td>
|
|
<td class="balance">{{ event['balance'] }}</td>
|
|
<td class="notes"></td>
|
|
</tr>
|
|
{% elif event['event'] == 'balance' %}
|
|
<tr>
|
|
<td class="bank"></td>
|
|
<td class="card"></td>
|
|
<td class="fees"></td>
|
|
<td class="credits"></td>
|
|
<td class="debits"></td>
|
|
<td class="balance">{{ event['balance'] }}</td>
|
|
<td class="notes"></td>
|
|
</tr>
|
|
{% elif event['event'] == 'credit' %}
|
|
<tr>
|
|
<td class="bank">{{ -event['amount'] }}</td>
|
|
<td class="card"></td>
|
|
<td class="fees">{{ event['fee'] }}</td>
|
|
<td class="credits"></td>
|
|
<td class="debits">{{ -event['amount'] + event['fee'] }}</td>
|
|
<td class="balance">{{ event['balance'] }}</td>
|
|
{% if event['recorder'] is None %}
|
|
<td class="notes">automatic withdrawal</td>
|
|
{% else %}
|
|
<td class="notes">
|
|
“{{ escape(event['note']) }}”—<a
|
|
href="/{{ event['recorder'] }}/">{{ event['recorder'] }}</a>
|
|
</td>
|
|
{% end %}
|
|
</tr>
|
|
{% elif event['event'] == 'charge' %}
|
|
<tr>
|
|
<td class="bank"></td>
|
|
<td class="card">{{ event['amount'] + event['fee'] }}</td>
|
|
<td class="fees">{{ event['fee'] }}</td>
|
|
<td class="credits">{{ event['amount'] }}</td>
|
|
<td class="debits"></td>
|
|
<td class="balance">{{ event['balance'] }}</td>
|
|
{% if event['recorder'] is None %}
|
|
<td class="notes">automatic charge</td>
|
|
{% else %}
|
|
<td class="notes">
|
|
“{{ escape(event['note']) }}”—<a
|
|
href="/{{ event['recorder'] }}/">{{ event['recorder'] }}</a>
|
|
</td>
|
|
{% end %}
|
|
</tr>
|
|
{% elif event['event'] == 'transfer' %}
|
|
<tr>
|
|
<td class="bank"></td>
|
|
<td class="card"></td>
|
|
<td class="fees"></td>
|
|
|
|
{% if event['tippee'] == participant.username %}
|
|
<td class="credits">{{ event['amount'] }}</td>
|
|
<td class="debits"></td>
|
|
{% else %}
|
|
<td class="credits"></td>
|
|
<td class="debits">{{ event['amount'] }}</td>
|
|
{% end %}
|
|
|
|
<td class="balance">{{ event['balance'] }}</td>
|
|
|
|
{% if event['tippee'] == participant.username %}
|
|
{% if user.ADMIN and (participant.username != user.username or 'override' in qs) %}
|
|
<td class="notes">from <a href="/{{ event['tipper'] }}/history/">{{ event['tipper'] }}</a></td>
|
|
{% else %}
|
|
<td class="notes">from someone</td>
|
|
{% end %}
|
|
{% else %}
|
|
{% if user.ADMIN %}
|
|
<td class="notes">to
|
|
<a href="/{{ event['tippee'] }}/history/">{{ event['tippee'] }}</a></td>
|
|
{% else %}
|
|
<td class="notes">to
|
|
<a href="/{{ event['tippee'] }}/">{{ event['tippee'] }}</a></td>
|
|
{% end %}
|
|
{% end %}
|
|
|
|
</tr>
|
|
{% end %}
|
|
{% end %}
|
|
</table>
|
|
|
|
{% end %}
|