X-Git-Url: https://git.hcoop.net/hcoop/portal.git/blobdiff_plain/f49e1088a3c5fbcc48bed202de376b407a23a9ba..c1c4be380263b5793c95deb162014930bd8aae6c:/money.sml diff --git a/money.sml b/money.sml index b5b20f1..8fa5ed7 100644 --- a/money.sml +++ b/money.sml @@ -11,7 +11,7 @@ type transaction = {id :int, descr : string, amount : real, d : string, stamp : fun mkTransactionRow [id, descr, amount, d, stamp] = {id = C.intFromSql id, descr = C.stringFromSql descr, amount = C.realFromSql amount, d = C.stringFromSql d, stamp = C.timestampFromSql stamp} - | mkTransactionRow row = raise Fail ("Bad transaction row : " ^ makeSet id row) + | mkTransactionRow row = Init.rowError ("transaction", row) fun addTransaction (descr, amount, d) = let @@ -19,8 +19,8 @@ fun addTransaction (descr, amount, d) = val id = nextSeq (db, "TransactionSeq") in C.dml db ($`INSERT INTO Transaction (id, descr, amount, d, stamp) - VALUES (^id, ^(C.stringToSql descr), ^(C.realToSql amount), ^(C.stringToSql d), CURRENT_TIMESTAMP)`); - C.intFromSql id + VALUES (^(C.intToSql id), ^(C.stringToSql descr), ^(C.realToSql amount), ^(C.stringToSql d), CURRENT_TIMESTAMP)`); + id end fun lookupTransaction id = @@ -83,10 +83,10 @@ val mkUserRow' = fn (trn :: rest) => (if C.isNull trn then false else true, mkUserRow rest) - | row => raise Fail ("Bad listUsers row: " ^ makeSet id row) + | row => Init.rowError ("listUsers", row) fun listUsers trn = - C.map (getDb ()) mkUserRow' ($`SELECT trn, id, name, rname, bal, joined + C.map (getDb ()) mkUserRow' ($`SELECT trn, id, name, rname, bal, joined, app, shares, paypal, checkout FROM WebUser LEFT OUTER JOIN Charge ON usr = id AND trn = ^(C.intToSql trn) ORDER BY name`) @@ -97,7 +97,7 @@ type charge = {trn : int, usr : int, amount : real} fun mkChargeRow [trn, usr, amount] = {trn = C.intFromSql trn, usr = C.intFromSql usr, amount = C.realFromSql amount} - | mkChargeRow row = raise Fail ("Bad charge row : " ^ makeSet id row) + | mkChargeRow row = Init.rowError ("charge", row) fun addCharge {trn, usr, amount} = ignore (C.dml (getDb ()) ($`INSERT INTO Charge (trn, usr, amount) @@ -109,7 +109,7 @@ fun listCharges trn = val mkChargeRow' = fn (name :: rest) => (C.stringFromSql name, mkChargeRow rest) - | row => raise Fail ("Bad name+charge row: " ^ makeSet id row) + | row => Init.rowError ("name+charge", row) fun listChargesWithNames trn = C.map (getDb ()) mkChargeRow' ($`SELECT name, trn, usr, amount FROM Charge, WebUser @@ -126,9 +126,9 @@ fun clearCharges trn = fun clearCharge [bal, amount] = ignore (C.dml db ($`UPDATE Balance - SET amount = amount - ^amount - WHERE id = ^bal`)) - | clearCharge row = raise Fail ("Bad clearCharge row : " ^ makeSet id row) + SET amount = amount - ^(C.stringFromSql amount) + WHERE id = ^(C.stringFromSql bal)`)) + | clearCharge row = Init.rowError ("clearCharge", row) in C.app db clearCharge ($`SELECT bal, SUM(amount) FROM Charge, WebUser WHERE trn = ^(C.intToSql trn) @@ -143,9 +143,9 @@ fun applyCharges trn = fun applyCharge [bal, amount] = ignore (C.dml db ($`UPDATE Balance - SET amount = amount + ^amount - WHERE id = ^bal`)) - | applyCharge row = raise Fail ("Bad applyCharge row : " ^ makeSet id row) + SET amount = amount + ^(C.stringFromSql amount) + WHERE id = ^(C.stringFromSql bal)`)) + | applyCharge row = Init.rowError ("applyCharge", row) in C.app db applyCharge ($`SELECT bal, SUM(amount) FROM Charge, WebUser WHERE trn = ^(C.intToSql trn) @@ -204,7 +204,7 @@ fun addHostingCharges {trn, cutoff, cost, usage} = in walkNvs (rest, SM.insert (umap, name, extra), - amount - extra) + amount + extra) end else walkNvs (rest, umap, amount) @@ -213,23 +213,149 @@ fun addHostingCharges {trn, cutoff, cost, usage} = val (umap, amount) = walkNvs (nvs, SM.empty, #amount tran) - val payers = Group.groupMembers paying - val even = amount / real (length payers) + val db = getDb () + + val shares = + case C.oneRow db ($`SELECT SUM(shares) + FROM WebUser JOIN Membership ON usr = WebUser.id AND grp = ^(C.intToSql paying)`) of + [n] => C.intFromSql n + | row => Init.rowError ("Bad addHostingCharges share count result", row) + + val even = amount / real shares - fun doUser (usr : Init.user, umap) = + fun doUser ([uid, uname, shares], umap) = let + val uid = C.intFromSql uid + val uname = C.stringFromSql uname + val shares = C.intFromSql shares + val (charge, umap) = - case SM.find (umap, #name usr) of - NONE => (even, umap) - | SOME extra => (even + extra, #1 (SM.remove (umap, #name usr))) + case SM.find (umap, uname) of + NONE => (even * real shares, umap) + | SOME extra => (even * real shares - extra, #1 (SM.remove (umap, uname))) in - addCharge {trn = trn, usr = #id usr, amount = charge}; + addCharge {trn = trn, usr = uid, amount = charge}; umap end + + val _ = if SM.numItems (C.fold db doUser umap + ($`SELECT id, name, shares + FROM WebUser JOIN Membership ON usr = WebUser.id AND grp = ^(C.intToSql paying)`)) + = 0 then + applyCharges trn + else + raise Fail "Usage description contains an unknown username" + + val usageFile = TextIO.openOut (Init.scratchDir ^ "/usage/" ^ Int.toString trn) + in + TextIO.output (usageFile, usage); + TextIO.closeOut usageFile + end + +fun equalizeBalances () = + ignore (C.dml (getDb ()) ($`UPDATE Balance SET amount = (SELECT SUM(amount) FROM Charge JOIN WebUser ON usr = WebUser.id WHERE bal = Balance.id)`)) + +fun lookupHostingUsage trn = + let + val usageFile = TextIO.openIn (Init.scratchDir ^ "/usage/" ^ Int.toString trn) + + fun loop acc = + case TextIO.inputLine usageFile of + NONE => String.concat (List.rev acc) + | SOME line => loop (line :: acc) in - if SM.numItems (foldl doUser umap payers) = 0 then - () - else - raise Fail "Usage description contains an unknown username" + SOME (loop []) + before TextIO.closeIn usageFile + end handle _ => NONE + +fun costBase amt = + case C.oneRow (getDb ()) ($`SELECT ^(C.realToSql amt) / SUM(shares) FROM WebUserPaying`) of + [share] => C.realFromSql share + | row => Init.rowError ("Bad costBase result", row) + +val monthlyCost = 900.0 +val graceMonths = 1 + +val baseDues = 7.0 + +fun delinquentPledgers () = + let + fun makeRow [id, name, shares, amount] = {id = C.intFromSql id, name = C.stringFromSql name, + shares = C.intFromSql shares, balance = C.realFromSql amount} + | makeRow row = Init.rowError ("Bad delinquentPledgers", row) + in + C.map (getDb ()) makeRow ($`SELECT WebUserPaying.id, WebUserPaying.name, shares, amount + FROM WebUserPaying JOIN Balance ON Balance.id = bal + WHERE amount < shares * ^(C.realToSql baseDues) * ^(C.intToSql graceMonths) + AND shares > 1 + ORDER BY name`) end -end \ No newline at end of file + +fun resetPledges ids = + ignore (C.dml (getDb ()) ($`UPDATE WebUser SET shares = 1 WHERE id IN (^(String.concatWith ", " (List.map C.intToSql ids)))`)) + +fun freezeworthyPledgers () = + let + fun makeRow [id, name, amount, j] = {id = C.intFromSql id, name = C.stringFromSql name, + balance = C.realFromSql amount, joinedThisMonth = C.boolFromSql j} + | makeRow row = Init.rowError ("Bad freezeworthyPledgers", row) + in + C.map (getDb ()) makeRow ($`SELECT WebUserPaying.id, WebUserPaying.name, amount, CURRENT_TIMESTAMP - joined < INTERVAL '1 month' + FROM WebUserPaying JOIN Balance ON Balance.id = bal + WHERE amount >= ^(C.realToSql baseDues) * ^(C.intToSql graceMonths) + AND amount < ^(C.realToSql baseDues) * ^(C.intToSql (graceMonths + 1)) + ORDER BY name`) + end + +fun bootworthyPledgers () = + let + fun makeRow [id, name, amount, j] = {id = C.intFromSql id, name = C.stringFromSql name, + balance = C.realFromSql amount, joinedThisMonth = C.boolFromSql j} + | makeRow row = Init.rowError ("Bad bootworthyPledgers", row) + in + C.map (getDb ()) makeRow ($`SELECT WebUserPaying.id, WebUserPaying.name, amount, CURRENT_TIMESTAMP - joined < INTERVAL '1 month' + FROM WebUserPaying JOIN Balance ON Balance.id = bal + WHERE amount < ^(C.realToSql baseDues) * ^(C.intToSql graceMonths) + ORDER BY name`) + end + +fun billDues {descr, base, date} = + let + val db = getDb () + val paying = + case Group.groupNameToId "paying" of + NONE => raise Fail "No 'paying' group" + | SOME id => id + + val shares = + case C.oneRow db ($`SELECT SUM(shares) + FROM WebUser JOIN Membership ON usr = WebUser.id AND grp = ^(C.intToSql paying)`) of + [n] => C.intFromSql n + | row => Init.rowError ("Bad addHostingCharges share count result", row) + + val total = real shares * base + + val give = addTransaction (descr, ~total, date) + + fun doUser [uid, shares] = + let + val uid = C.intFromSql uid + val shares = C.intFromSql shares + in + addCharge {trn = give, usr = uid, amount = ~(base * real shares)} + end + | doUser r = Init.rowError ("Bad billDues/doUser row", r) + + val receive = addTransaction (descr, total, date) + + val hcoop = valOf (Init.userNameToId "hcoop") + in + C.app db doUser ($`SELECT id, shares + FROM WebUser JOIN Membership ON usr = WebUser.id AND grp = ^(C.intToSql paying)`); + applyCharges give; + + addCharge {trn = receive, usr = hcoop, amount = total}; + applyCharges receive + end + +end