diff --git a/gratipay/billing/payday.py b/gratipay/billing/payday.py index 0063642428..d10e80d2da 100644 --- a/gratipay/billing/payday.py +++ b/gratipay/billing/payday.py @@ -162,6 +162,7 @@ def payin(self): self.db.run(""" DROP FUNCTION process_take(); DROP FUNCTION process_tip(); + DROP FUNCTION settle_tip_graph(); DROP FUNCTION transfer(text, text, numeric, context_type); """) @@ -324,6 +325,33 @@ def prepare(cursor, ts_start): FOR EACH ROW EXECUTE PROCEDURE process_take(); + -- Create a function to settle whole tip graph + + CREATE OR REPLACE FUNCTION settle_tip_graph() RETURNS void AS $$ + DECLARE + count integer NOT NULL DEFAULT 0; + i integer := 0; + BEGIN + LOOP + i := i + 1; + WITH updated_rows AS ( + UPDATE payday_tips + SET is_funded = true + WHERE is_funded IS NOT true + RETURNING * + ) + SELECT COUNT(*) FROM updated_rows INTO count; + IF (count = 0) THEN + EXIT; + END IF; + IF (i > 50) THEN + RAISE 'Reached the maximum number of iterations'; + END IF; + END LOOP; + END; + $$ LANGUAGE plpgsql; + + -- Save the stats we already have UPDATE paydays @@ -425,9 +453,7 @@ def transfer_tips(cursor): WHERE p.username = t.tipper AND p.card_hold_ok; - UPDATE payday_tips t - SET is_funded = true - WHERE is_funded IS NOT true; + SELECT settle_tip_graph(); """) @@ -455,6 +481,8 @@ def transfer_takes(cursor, ts_start): ) IS NULL ORDER BY t.team, t.ctime DESC; + SELECT settle_tip_graph(); + """, dict(ts_start=ts_start)) diff --git a/tests/py/test_billing_payday.py b/tests/py/test_billing_payday.py index e10ba738fa..c4a9c2a442 100644 --- a/tests/py/test_billing_payday.py +++ b/tests/py/test_billing_payday.py @@ -366,6 +366,33 @@ def test_transfer_tips(self): assert Participant.from_id(self.janet.id).balance == D('0.51') assert Participant.from_id(self.homer.id).balance == 0 + def test_transfer_tips_whole_graph(self): + alice = self.make_participant('alice', claimed_time='now', balance=0, + last_bill_result='') + alice.set_tip_to(self.homer, D('50')) + self.homer.set_tip_to(self.janet, D('20')) + self.janet.set_tip_to(self.david, D('5')) + self.david.set_tip_to(self.homer, D('20')) # Should be unfunded + + payday = Payday.start() + with self.db.get_cursor() as cursor: + payday.prepare(cursor, payday.ts_start) + cursor.run("""UPDATE payday_participants + SET card_hold_ok = true + WHERE id = %s + """, (alice.id,)) + payday.transfer_tips(cursor) + cursor.run("""UPDATE payday_participants + SET new_balance = 0 + WHERE id = %s + """, (alice.id,)) + payday.update_balances(cursor) + alice = Participant.from_id(alice.id) + assert Participant.from_id(alice.id).balance == D('0') + assert Participant.from_id(self.homer.id).balance == D('30') + assert Participant.from_id(self.janet.id).balance == D('15') + assert Participant.from_id(self.david.id).balance == D('5') + def test_transfer_takes(self): a_team = self.make_participant('a_team', claimed_time='now', number='plural', balance=20) alice = self.make_participant('alice', claimed_time='now')