Update low balance reminder for new deposit regime
[bpt/portal.git] / remind / remind.sml
1 structure Remind :> REMIND =
2 struct
3
4 open Config
5
6 structure C = PgClient
7
8 fun printReal n =
9 if n < 0.0 then
10 "-" ^ Real.fmt (StringCvt.FIX (SOME 2)) (~n)
11 else
12 Real.fmt (StringCvt.FIX (SOME 2)) n
13
14 fun main _ =
15 let
16 val db = C.conn dbstring
17
18 val totalShares = case C.oneRow db "SELECT SUM(shares) FROM WebUserPaying" of
19 [total] => C.intFromSql total
20 | _ => raise Fail "Bad SUM(shares) row"
21
22 fun doOne [user, members, shares, amount] =
23 let
24 val user = C.stringFromSql user
25 val members = C.intFromSql members
26 val shares = C.intFromSql shares
27 val amount = C.realFromSql amount
28
29 val perMonth = 900.0 * real shares / real totalShares
30 val deposit = perMonth * 3.0
31 val headsUp = perMonth * 5.0
32 in
33 if amount >= headsUp then
34 ()
35 else
36 let
37 val m = Mail.mopen ()
38 fun write msg = Mail.mwrite (m, msg)
39 in
40 if amount < deposit then
41 write "Subject: Your NEGATIVE HCoop balance\n"
42 else
43 write "Subject: Your low HCoop balance\n";
44 write "From: HCoop Payment Reminder Robot <payment";
45 write emailSuffix;
46 write ">\n";
47 write "To: ";
48 write user;
49 write emailSuffix;
50 write "\nCc: payment@hcoop.net";
51 write "\n\n";
52
53 if amount < deposit then
54 (write "Your HCoop balance is negative. This means that you've paid less than you've\n";
55 write "been charged to date, excluding your required deposit. If your account hasn't\n";
56 write "been frozen yet, expect that to happen very soon. Our bylaws allow our board\n";
57 write "of directors to vote you out of the co-op, without any obligation to contact\n";
58 write "you first, when your balance stays negative for three months. Please make a\n";
59 write "payment as soon as possible, so that we don't need to do this!\n\n")
60 else
61 (write "With our current dues projections, you have less than two months left until\n";
62 write "your HCoop balance becomes negative, based on your sliding scale pledge amount.\n";
63 write "Please make a payment as soon as you can. We will freeze your account if your\n";
64 write "balance does become negative, and the board of directors will probably vote you\n";
65 write "out of the cooperative shortly thereafter if you don't make a payment.\n\n");
66
67 write "Your balance: US$";
68 write (printReal (amount - deposit));
69 write "\nTotal number of members linked to your balance: ";
70 write (Int.toString members);
71 write "\nTotal pledge amount: ";
72 write (Int.toString shares);
73 write "\nDeposit: US$";
74 write (printReal deposit);
75 write "\nMinimum amount to pay to not see this message again for two months: US$";
76 write (printReal (headsUp - amount));
77
78 write "\n\nYour deposit requirement was calculated by dividing our total monthly expenses\n";
79 write "($900) by the sum of all members' pledge amounts, multiplying by your pledge amount,\n";
80 write "and then multiplying by 3. That is, the amount covers your share of three months'\n";
81 write "expenses.\n\n";
82
83 write "To make a payment, visit:\n";
84 write " https://members.hcoop.net/\n";
85 write "and use the PayPal or Google Checkout link.\n";
86
87 write "\nIf for whatever reason you don't plan to pay the amount suggested in this e-mail,\n";
88 write "_please_ don't stay silent. Reply to this message explaining your circumstances.\n";
89 write "We are doing limited-time monetary grants on request, due to the extra costs\n";
90 write "associated with setting up our new servers.\n";
91
92 ignore (Mail.mclose m)
93 end
94 end
95 | doOne _ = raise Fail "Bad SQL row"
96 in
97 C.app db doOne "SELECT Balance.name, COUNT(*), SUM(WebUserPaying.shares) AS shrs, Balance.amount FROM WebUserPaying JOIN Balance ON WebUserPaying.bal = Balance.id GROUP BY Balance.name, Balance.amount HAVING amount < 10";
98 C.close db;
99 OS.Process.success
100 end handle C.Sql s => (print ("SQL failure: " ^ s ^ "\n");
101 OS.Process.failure)
102
103 end