Repository: liberapay/liberapay.com

Issue:
Increase the default nominal take from zero
For teams that only have one member, it may not make sense to the user to check that their nominal take is set above 0.00, because why look at takes if you're the only one taking? The assumption may be that all donations will come to you automatically.

I suggest that the nominal take be set at some reasonable amount (10? 100?) for the first team member. Then when that team member goes to add a member, a notice could be displayed that shows the takes table and suggests modifying this value.

See, for example, https://liberapay.com/SocialCoop/income/.

(Mistakenly posted this at https://github.com/liberapay/liberapay.org/issues/24 first.)


Discussion:
https://github.com/liberapay/liberapay.com/blob/master/liberapay/models/_mixin_team.py
I would prefer that we work on properly improving [team management](https://github.com/liberapay/liberapay.com/milestone/3) for everyone instead of implementing this small hack for single-member teams.

For example we should improve notifications so that team members are made aware of their teams' leftovers every week.
Even when there are multiple users, I'm not sure the default intake should be 0. Maybe it should be (amount received by team / number of members), or (leftover / number of members).

I had a look at some teams and I see that sometimes people don't dare to increase their intake. Maybe they feel their contribution to the team doesn't deserve taking money from it? If there was instead a default amount that shares the money somewhat evenly, they could make the decision to get less than the default, but still take something.

Resolving patch:
diff --git a/liberapay/billing/payday.py b/liberapay/billing/payday.py
--- a/liberapay/billing/payday.py
+++ b/liberapay/billing/payday.py
@@ -227,7 +227,7 @@ def prepare(cursor, ts_start):
                    ORDER BY team, member, mtime DESC
                    ) t
              WHERE t.amount IS NOT NULL
-               AND t.amount > 0
+               AND t.amount <> 0
                AND t.team IN (SELECT id FROM payday_participants)
                AND t.member IN (SELECT id FROM payday_participants);
 
@@ -418,11 +418,23 @@ def resolve_takes(tips, takes, ref_currency):
         total_income = MoneyBasket(t.full_amount for t in tips)
         if total_income == 0:
             return (), total_income
-        takes = [t for t in takes if t.amount > 0]
-        total_takes = MoneyBasket(t.amount for t in takes)
-        if total_takes == 0:
+        takes = [t for t in takes if t.amount != 0]
+        if not takes:
             return (), total_income
         fuzzy_income_sum = total_income.fuzzy_sum(ref_currency)
+        manual_takes = [t for t in takes if t.amount > 0]
+        if manual_takes:
+            manual_takes_sum = MoneyBasket(t.amount for t in manual_takes)
+            auto_take = fuzzy_income_sum - manual_takes_sum.fuzzy_sum(ref_currency)
+            if auto_take < 0:
+                auto_take = auto_take.zero()
+        else:
+            auto_take = fuzzy_income_sum
+        for take in takes:
+            if take.amount < 0:
+                take.amount = auto_take.convert(take.amount.currency)
+            assert take.amount >= 0
+        total_takes = MoneyBasket(t.amount for t in takes)
         fuzzy_takes_sum = total_takes.fuzzy_sum(ref_currency)
         tips_by_currency = group_by(tips, lambda t: t.full_amount.currency)
         takes_by_preferred_currency = group_by(takes, lambda t: t.main_currency)
diff --git a/liberapay/models/_mixin_team.py b/liberapay/models/_mixin_team.py
--- a/liberapay/models/_mixin_team.py
+++ b/liberapay/models/_mixin_team.py
@@ -41,7 +41,7 @@ def add_member(self, member, cursor=None):
             raise MemberLimitReached
         if member.status != 'active':
             raise InactiveParticipantAdded
-        self.set_take_for(member, Money.ZEROS[member.main_currency], self, cursor=cursor)
+        self.set_take_for(member, Money(-1, member.main_currency), self, cursor=cursor)
 
     def remove_all_members(self, cursor=None):
         (cursor or self.db).run("""
@@ -68,7 +68,7 @@ def get_takes_last_week(self):
         assert self.kind == 'group'
         takes = OrderedDict((t.member, t.amount) for t in self.db.all("""
 
-            SELECT DISTINCT ON (member) member, amount, mtime
+            SELECT DISTINCT ON (member) member, amount
               FROM takes
              WHERE team=%s
                AND mtime < (
@@ -80,7 +80,8 @@ def get_takes_last_week(self):
           ORDER BY member, mtime DESC
 
         """, (self.id,)) if t.amount)
-        takes.sum = MoneyBasket(takes.values())
+        takes.nonzero = any(amount != 0 for amount in takes.values())
+        takes.sum = MoneyBasket(amount for amount in takes.values() if amount > 0)
         takes.initial_leftover = self.receiving - takes.sum.fuzzy_sum(self.main_currency)
         return takes
 
@@ -124,7 +125,7 @@ def set_take_for(self, member, take, recorder, check_max=True, cursor=None):
             if take and check_max and self.throttle_takes and self.nmembers > 1:
                 if take > TAKE_THROTTLING_THRESHOLD[take.currency]:
                     last_week = self.get_takes_last_week()
-                    if last_week.sum:
+                    if last_week.nonzero:
                         max_this_week = self.compute_max_this_week(
                             member.id, last_week, take.currency
                         )
@@ -291,17 +292,16 @@ def get_members(self):
         takes = self.get_current_takes()
         nmembers = len(takes)
         last_week = self.get_takes_last_week()
-        compute_max = self.throttle_takes and nmembers > 1 and last_week.sum
+        compute_max = self.throttle_takes and nmembers > 1 and last_week.nonzero
         members = OrderedDict()
         members.leftover = self.leftover or MoneyBasket()
-        zero = Money.ZEROS[self.main_currency]
         for take in takes:
             member = {}
             m_id = member['id'] = take['member_id']
             member['username'] = take['member_name']
             member['nominal_take'] = take['amount']
             member['actual_amount'] = take['actual_amount']
-            member['last_week'] = last_week.get(m_id, zero)
+            member['last_week'] = last_week.get(m_id)
             if compute_max:
                 x = self.compute_max_this_week(m_id, last_week, take['amount'].currency)
             else:
diff --git a/liberapay/payin/common.py b/liberapay/payin/common.py
--- a/liberapay/payin/common.py
+++ b/liberapay/payin/common.py
@@ -161,6 +161,7 @@ def resolve_destination(db, tippee, provider, payer, payer_country, payin_amount
                ), 'EUR')) AS takes_sum_eur
           FROM current_takes t
          WHERE t.team = %s
+           AND t.amount <> 0
            AND EXISTS (
                    SELECT true
                      FROM payment_accounts a
@@ -174,9 +175,10 @@ def resolve_destination(db, tippee, provider, payer, payer_country, payin_amount
     if not members:
         raise MissingPaymentAccount(tippee)
     payin_amount_eur = payin_amount.convert('EUR')
+    zero_eur = Money.ZEROS['EUR']
     members = sorted(members, key=lambda t: (
         int(t.member == payer.id),
-        -t.takes_sum_eur / (t.received_sum_eur + payin_amount_eur),
+        -max(t.takes_sum_eur, zero_eur) / (t.received_sum_eur + payin_amount_eur),
         t.received_sum_eur,
         t.ctime
     ))


Test patch:
diff --git a/tests/py/test_payday.py b/tests/py/test_payday.py
--- a/tests/py/test_payday.py
+++ b/tests/py/test_payday.py
@@ -118,9 +118,9 @@ def check():
             assert carl.receiving == EUR('0.00')
             assert carl.npatrons == 0
             assert carl.nteampatrons == 0
-            assert dana.receiving == EUR('3.00')
+            assert dana.receiving == EUR('3.49')
             assert dana.npatrons == 1
-            assert dana.nteampatrons == 0
+            assert dana.nteampatrons == 1
             assert emma.receiving == EUR('0.50')
             assert emma.npatrons == 1
             assert emma.nteampatrons == 0
@@ -135,7 +135,7 @@ def check():
             team2 = Participant.from_username('team2')
             assert team2.receiving == EUR('0.49')
             assert team2.npatrons == 1
-            assert team2.leftover == EUR('0.49')
+            assert team2.leftover == EUR('0.00')
 
             janet = self.janet.refetch()
             assert janet.giving == 0