mirror of
https://github.com/kennethreitz-archive/www.gittip.com.git
synced 2026-06-21 15:50:59 +00:00
be8dc4f8ce
Sometimes we do manual payouts (maybe payins someday?) and we need a way to record those in the database so we don't have to keep doing them directly in SQL(!). This adds a form to the history page for site admins to use. It extends the exchanges schema to keep track of who made manual adjustments and why.
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, participant_id, balance):
|
|
self._participant_id = participant_id
|
|
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_id=%s "
|
|
"ORDER BY timestamp ASC"
|
|
, (participant_id,)
|
|
))
|
|
self._transfers = list(db.fetchall( "SELECT * FROM transfers "
|
|
"WHERE tipper=%s OR tippee=%s "
|
|
"ORDER BY timestamp ASC"
|
|
, (participant_id, participant_id)
|
|
))
|
|
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._participant_id:
|
|
|
|
# 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._participant_id:
|
|
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.id, participant.balance)
|
|
hero = "History"
|
|
title = "%s - %s" % (participant.id, hero)
|
|
locked = False
|
|
|
|
# ========================================================================== ^L
|
|
{% extends templates/profile.html %}
|
|
{% block page %}
|
|
|
|
<table id="history" class="centered">
|
|
<tr><td colspan="7">
|
|
<h2>{{ participant.id == user.id and "You" or participant.id }} joined
|
|
{{ to_age(participant.claimed_time) }}.</h2>
|
|
</td></tr>
|
|
<tr><td colspan="7">
|
|
|
|
<h2>{{ participant.id == user.id 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.id %}
|
|
<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.id %}
|
|
{% if user.ADMIN and (participant.id != user.id or 'override' in qs) %}
|
|
<td class="notes">from <a href="/{{ event['tipper'] }}/history.html">{{ 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.html">{{ event['tippee'] }}</a></td>
|
|
{% else %}
|
|
<td class="notes">to
|
|
<a href="/{{ event['tippee'] }}/">{{ event['tippee'] }}</a></td>
|
|
{% end %}
|
|
{% end %}
|
|
|
|
</tr>
|
|
{% end %}
|
|
{% end %}
|
|
</table>
|
|
|
|
{% end %}
|