--- /dev/null
+-- Make sure that money.materialized_payment_by_billing_type can handle what's thrown at it
+
+\set mmpbt materialized_payment_by_billing_type
+\set mbxs materialized_billable_xact_summary
+
+\set patron 109
+\set staff 223
+
+
+-- convenince funcs
+
+CREATE OR REPLACE FUNCTION fake_it_make_it(id BIGINT, patron BIGINT, staff BIGINT, copy BIGINT) RETURNS BOOLEAN
+AS $$
+
+ INSERT INTO action.circulation
+ (id, usr, xact_start, xact_finish, target_copy, circ_lib, circ_staff, renewal_remaining,
+ grace_period, due_date, duration_rule, recurring_fine_rule, max_fine_rule)
+ VALUES
+ (id, patron, (now() - '4 weeks'::interval), null, copy, 5, staff, 0,
+ '1 day', (now() - '3 weeks'::interval), 1, 1, 1);
+
+ SELECT TRUE;
+
+$$ LANGUAGE SQL;
+
+
+CREATE OR REPLACE FUNCTION u_owe_me(xact BIGINT, days_ago INT, amount NUMERIC(6,2)) RETURNS BOOLEAN
+AS $$
+
+ INSERT INTO money.billing
+ (xact, billing_ts, amount, billing_type, btype)
+ VALUES
+ (xact, (now() - (days_ago::TEXT || ' days')::interval), COALESCE(amount, 0.25), 'srsly', 1);
+
+ SELECT TRUE;
+
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION spray_bills(xact BIGINT, num_bills INT, amount NUMERIC(6,2)) RETURNS BOOLEAN
+AS $$
+BEGIN
+ LOOP
+ PERFORM u_owe_me(xact, num_bills, amount);
+ num_bills := num_bills -1;
+ EXIT WHEN num_bills = 0;
+ END LOOP;
+
+ RETURN TRUE;
+
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION un_atm(xact BIGINT, days_ago INT, type TEXT, amount NUMERIC(6,2)) RETURNS BOOLEAN
+AS $$
+BEGIN
+
+ amount := COALESCE(amount, 0.25);
+
+ EXECUTE $E$INSERT INTO money.$E$ || type || $E$ (xact, payment_ts, amount_collected, amount, accepting_usr) $E$ ||
+ $E$VALUES ($E$ || xact || $E$, (now() - '$E$ || days_ago::TEXT || $E$ days'::interval), $E$ || amount || $E$, $E$ || amount || $E$, 1)$E$;
+
+ RETURN TRUE;
+
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION make_it_rain(xact BIGINT, num_pays INT, type TEXT, amount NUMERIC(6,2)) RETURNS BOOLEAN
+AS $$
+BEGIN
+
+ LOOP
+ PERFORM un_atm(xact, num_pays, type, amount);
+ num_pays := num_pays -1;
+ EXIT WHEN num_pays = 0;
+ END LOOP;
+
+ RETURN TRUE;
+
+END;
+$$ LANGUAGE plpgsql;
+
+
+
+-- pgTAP Time
+
+BEGIN;
+
+SELECT * FROM no_plan();
+-- SELECT plan(); -- come back to this...
+
+-- Scenario setup
+
+-- no billing, no payment, no problem
+
+\set id -1000
+\set copy 401
+
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 0, 'No billings, no payments, no mmpbt');
+
+-- 1 billing, no payment
+
+\set id -1001
+\set copy 408
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT u_owe_me(:id, 21);
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 0, 'No payment, no mmpbt');
+
+
+-- 1 bill, 1 complete payment
+
+\set id -1002
+\set copy 415
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT u_owe_me(:id, 21);
+SELECT un_atm(:id, 21, 'cash_payment', NULL);
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 1, '1 payment, 1 entry');
+
+
+-- 1 bill 1 partial paynent
+
+\set id -1003
+\set copy 422
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT u_owe_me(:id, 21);
+SELECT un_atm(:id, 21, 'cash_payment', 0.15);
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 1, '1 partial payment, 1 entry');
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id AND amount = 0.15)::INTEGER, 1, '... for the right amount');
+
+
+-- 1 bill, multiple payments, complete
+
+\set id -1004
+\set copy 429
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT u_owe_me(:id, 21);
+SELECT un_atm(:id, 21, 'cash_payment', 0.15);
+SELECT un_atm(:id, 20, 'cash_payment', 0.10);
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 2, '1 partial payment, 1 entry');
+SELECT is( (SELECT sum(amount) FROM money.:"mmpbt" WHERE xact = :id ), 0.25, '... for the right amount');
+
+-- 2 bills, 1 payment, complete
+
+\set id -1005
+\set copy 436
+
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT spray_bills(:id, 2, NULL);
+SELECT un_atm(:id, 21, 'cash_payment', 0.50);
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 2, '2 bills, 1 complete payment');
+SELECT ok( (SELECT total_paid FROM money.:"mbxs" WHERE id = :id) =
+ (SELECT SUM(amount) FROM money.:"mmpbt" WHERE xact = :id), 'correct amounts');
+
+-- 2 bills, 3 payments, partial
+-- bill 1, payment 1, .25
+-- bill 1, payment 2, .25
+-- bill 2, payment 2, .05
+-- bill 2, payment 3, .20
+
+\set id -1006
+\set copy 443
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT spray_bills(:id, 2, 0.50); -- $1
+SELECT un_atm(:id, 21, 'cash_payment', 0.25);
+SELECT un_atm(:id, 20, 'cash_payment', 0.30);
+SELECT un_atm(:id, 19, 'cash_payment', 0.20); -- $.75, spread across 4 entries
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 4, '2 bills, 2 partial payments spread across them');
+SELECT ok( (SELECT total_paid FROM money.:"mbxs" WHERE id = :id) =
+ (SELECT SUM(amount) FROM money.:"mmpbt" WHERE xact = :id), 'correct amounts');
+
+-- 2 bills, 1 void, 1 paid in full
+
+\set id -1007
+\set copy 450
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT spray_bills(:id, 2, NULL); -- $.50
+UPDATE money.billing SET voided = TRUE, voider = 1, void_time = now() WHERE xact = :id AND
+ id IN (SELECT MIN(id) FROM money.billing WHERE xact = :id LIMIT 1);
+SELECT un_atm(:id, 21, 'cash_payment', NULL);
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 1, '1 unvoided bill, 1 full payment');
+SELECT ok( (SELECT total_paid FROM money.:"mbxs" WHERE id = :id) =
+ (SELECT SUM(amount) FROM money.:"mmpbt" WHERE xact = :id), 'correct amount');
+
+-- 2 bills, 1 voided, 1 adjusted
+
+\set id -1008
+\set copy 457
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT spray_bills(:id, 2, NULL); -- $.50
+UPDATE money.billing SET voided = TRUE, voider = 1 WHERE xact = :id AND id IN (SELECT MIN(id) FROM money.billing WHERE xact = :id LIMIT 1);
+SELECT un_atm(:id, 21, 'account_adjustment', NULL);
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 1, '1 unvoided bill, 1 adjusted payment');
+SELECT ok( (SELECT total_paid FROM money.:"mbxs" WHERE id = :id) =
+ (SELECT SUM(amount) FROM money.:"mmpbt" WHERE xact = :id), 'correct amount');
+
+
+
+-- a simplification of a real transaction that caused problems in the past
+-- 45 voided billings of various sizes, 2 non-voided billings for 32.20,
+-- 38 account_adjustments for .25 amount, 1 for 22.70, and 1 forgive_payment for 32.20
+
+\set id -1009
+\set copy 471
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT spray_bills(:id, 20, NULL);
+SELECT spray_bills(:id, 20, NULL);
+SELECT u_owe_me(:id, 6, 15.00);
+SELECT u_owe_me(:id, 5, 17.20);
+SELECT u_owe_me(:id, 4, 9.50);
+SELECT u_owe_me(:id, 3, 32.20);
+SELECT u_owe_me(:id, 3, 6.00);
+UPDATE money.billing SET voided = TRUE, voider = 1, void_time = now() WHERE xact = :id;
+SELECT u_owe_me(:id, 1, 32.20);
+SELECT u_owe_me(:id, 1, 32.20);
+
+SELECT make_it_rain(:id, 20, 'account_adjustment', 0.25);
+SELECT make_it_rain(:id, 18, 'account_adjustment', 0.25);
+SELECT un_atm(:id, 1, 'account_adjustment', 22.70);
+SELECT un_atm(:id, 1, 'forgive_payment', 32.20);
+
+SELECT is( (SELECT COUNT(DISTINCT billing) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 2, 'Simplified real transaction bills');
+SELECT is( (SELECT COUNT(DISTINCT payment) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 40, 'Simplified real transaction payments');
+SELECT ok( (SELECT total_paid FROM money.:"mbxs" WHERE id = :id) =
+ (SELECT SUM(amount) FROM money.:"mmpbt" WHERE xact = :id), 'correct amount');
+
+-- DEBUGGERY
+\o mmpbt_out
+\t
+\a
+SELECT * FROM money.:"mmpbt" WHERE xact = :id;
+SELECT '';
+SELECT * FROM money.billing WHERE xact = :id;
+SELECT '';
+SELECT * FROM money.bnm_payment_view WHERE xact = :id;
+\a
+\t
+\o
+
+
+-- BANANAS
+-- You can't realistically cause some of these situations in the client,
+-- but they're still no reason for things to break.
+
+-- 0 bills, mystery payment
+
+\set id -1010
+\set copy 478
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT un_atm(:id, 21, 'cash_payment', NULL);
+
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 0, '0 bills, 1 unexpected payment');
+
+-- 1 bill, 1 extra large payment
+
+\set id -1011
+\set copy 485
+SELECT fake_it_make_it(:id, :patron, :staff, :copy);
+SELECT u_owe_me(:id, 21, 0.50);
+SELECT un_atm(:id, 21, 'cash_payment', 0.75);
+SELECT is( (SELECT COUNT(id) FROM money.:"mmpbt" WHERE xact = :id)::INTEGER, 1, '1 bill, too large payment');
+SELECT ok( (SELECT total_owed FROM money.:"mbxs" WHERE id = :id) = -- Note you can't use total_paid here, because it's busted, remember?
+ (SELECT SUM(amount) FROM money.:"mmpbt" WHERE xact = :id), 'correct amount');
+
+
+
+
+
+
+
+
+
+
+SELECT * FROM finish();
+ROLLBACK;
+
+-- cleanup
+DROP FUNCTION fake_it_make_it(BIGINT, BIGINT, BIGINT, BIGINT);
+DROP FUNCTION u_owe_me(BIGINT, INT, NUMERIC(6,2));
+DROP FUNCTION spray_bills(BIGINT, INT, NUMERIC(6,2));
+DROP FUNCTION un_atm(BIGINT, INT, TEXT, NUMERIC(6,2));
+DROP FUNCTION make_it_rain(BIGINT, INT, TEXT, NUMERIC(6,2));
+