Repository: liberapay/liberapay.com

Issue:
Incorrect `npatrons` value on some stub accounts
> **0** Liberapay-uzanto**j** promesis donaci entute **€ 0,26** semajne al nayafia.

https://eo.liberapay.com/on/twitter/nayafia
Which means: «**0** user**S** promised **€ 0,23**»

Same in spanish:
https://es.liberapay.com/on/twitter/nayafia
«**0** usuario**s** de Liberapay han prometido donar un total de **0,26 €** por semana a nayafia.»
And in english!
https://en.liberapay.com/on/twitter/nayafia
but in french it's okay
https://fr.liberapay.com/on/twitter/nayafia

Other instances of the issue:
https://eo.liberapay.com/on/github/kanboard
https://eo.liberapay.com/on/github/BurntSushi
https://eo.liberapay.com/on/github/kalev

On a sample of 154 pledges (wow I'm gonna bankrupt if they all join Liberapay ^^")
Only happened on the cases where one person has pledge.

But not on all, in fact, most of the cases with 1 pledge are working fine

https://eo.liberapay.com/on/github/LaQuadratureDuNet
https://eo.liberapay.com/on/twitter/BryanLunduke
https://eo.liberapay.com/on/twitter/AgendaduLibreFr
https://eo.liberapay.com/on/twitter/WineHQ

Very weird, can't find the common denominator


Discussion:
Ho, I see, it's the plural equations that are different between languages.:

https://hosted.weblate.org/translate/liberapay/core/eo/?checksum=cb1eb810b8117e81
 Plural equation: n != 1 

https://hosted.weblate.org/translate/liberapay/core/es/?checksum=cb1eb810b8117e81
 Plural equation: n != 1 

https://hosted.weblate.org/translate/liberapay/core/fr/?checksum=cb1eb810b8117e81
 Plural equation: n > 1 

That doesn't explain why there are cases that work and others that don't.

Resolving patch:
diff --git a/liberapay/billing/payday.py b/liberapay/billing/payday.py
--- a/liberapay/billing/payday.py
+++ b/liberapay/billing/payday.py
@@ -849,13 +849,13 @@ def update_cached_amounts(cls):
             cls.prepare(cursor, now)
             cls.transfer_virtually(cursor, now)
             cursor.run("""
-
             UPDATE tips t
                SET is_funded = t2.is_funded
               FROM payday_tips t2
              WHERE t.id = t2.id
                AND t.is_funded <> t2.is_funded;
-
+            """)
+            cursor.run("""
             WITH active_donors AS (
                      SELECT DISTINCT tr.tipper AS id
                        FROM transfers tr
@@ -877,10 +877,13 @@ def update_cached_amounts(cls):
                    ) t2
              WHERE t2.id = t.id
                AND t.is_funded <> t2.is_funded;
-
+            """)
+            cursor.run("""
             UPDATE participants p
                SET receiving = p2.receiving
+                 , npatrons = p2.npatrons
               FROM ( SELECT p2.id
+                          , count(*) AS npatrons
                           , coalesce_currency_amount(
                                 sum(t.amount, p2.main_currency),
                                 p2.main_currency
@@ -893,8 +896,10 @@ def update_cached_amounts(cls):
                    ) p2
              WHERE p.id = p2.id
                AND p.receiving <> p2.receiving
+               AND p.npatrons <> p2.npatrons
                AND p.status = 'stub';
-
+            """)
+            cursor.run("""
             UPDATE takes t
                SET actual_amount = t2.actual_amount
               FROM ( SELECT t2.id
@@ -909,7 +914,8 @@ def update_cached_amounts(cls):
                    ) t2
              WHERE t.id = t2.id
                AND t.actual_amount <> t2.actual_amount;
-
+            """)
+            cursor.run("""
             UPDATE participants p
                SET giving = p2.giving
               FROM ( SELECT p2.id
@@ -923,7 +929,8 @@ def update_cached_amounts(cls):
                    ) p2
              WHERE p.id = p2.id
                AND p.giving <> p2.giving;
-
+            """)
+            cursor.run("""
             UPDATE participants p
                SET taking = p2.taking
               FROM ( SELECT p2.id
@@ -937,7 +944,8 @@ def update_cached_amounts(cls):
                    ) p2
              WHERE p.id = p2.id
                AND p.taking <> p2.taking;
-
+            """)
+            cursor.run("""
             UPDATE participants p
                SET receiving = p2.receiving
               FROM ( SELECT p2.id
@@ -952,7 +960,8 @@ def update_cached_amounts(cls):
              WHERE p.id = p2.id
                AND p.receiving <> p2.receiving
                AND p.status <> 'stub';
-
+            """)
+            cursor.run("""
             UPDATE participants p
                SET leftover = p2.leftover
               FROM ( SELECT p2.id
@@ -971,7 +980,8 @@ def update_cached_amounts(cls):
                    ) p2
              WHERE p.id = p2.id
                AND p.leftover <> p2.leftover;
-
+            """)
+            cursor.run("""
             UPDATE participants p
                SET nteampatrons = p2.nteampatrons
               FROM ( SELECT p2.id
@@ -986,7 +996,8 @@ def update_cached_amounts(cls):
                    ) p2
              WHERE p.id = p2.id
                AND p.nteampatrons <> p2.nteampatrons;
-
+            """)
+            cursor.run("""
             UPDATE participants p
                SET npatrons = p2.npatrons
               FROM ( SELECT p2.id
@@ -1000,7 +1011,6 @@ def update_cached_amounts(cls):
                    ) p2
              WHERE p.id = p2.id
                AND p.npatrons <> p2.npatrons;
-
             """)
         cls.clean_up()
         log("Updated receiving amounts.")


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
@@ -192,8 +192,7 @@ def check():
              WHERE mangopay_user_id IS NOT NULL;
             UPDATE participants
                SET npatrons = 10000
-                 , receiving = (10000,'EUR')
-             WHERE status = 'active';
+                 , receiving = (10000,'EUR');
         """)
         Payday.start().update_cached_amounts()
         check()