3 SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
6 CREATE TABLE money.materialized_payment_by_billing_type (
7 id BIGSERIAL PRIMARY KEY,
9 payment BIGINT NOT NULL,
10 billing BIGINT NOT NULL,
11 payment_ts TIMESTAMPTZ NOT NULL,
12 billing_ts TIMESTAMPTZ NOT NULL,
13 amount NUMERIC(8,2) NOT NULL,
18 CONSTRAINT x_p_b_once UNIQUE (xact,payment,billing)
21 CREATE INDEX p_by_b_payment_ts_idx
22 ON money.materialized_payment_by_billing_type (payment_ts);
24 CREATE OR REPLACE FUNCTION money.payment_by_billing_type (
26 ) RETURNS SETOF money.materialized_payment_by_billing_type AS $$
28 current_result money.materialized_payment_by_billing_type%ROWTYPE;
29 current_payment money.payment_view%ROWTYPE;
30 current_billing money.billing%ROWTYPE;
31 payment_remainder NUMERIC(8,2) := 0.0;
32 billing_remainder NUMERIC(8,2) := 0.0;
33 payment_offset INT := 0;
34 billing_offset INT := 0;
37 fast_forward BOOLEAN := FALSE;
38 maintain_billing_remainder BOOLEAN := FALSE;
39 billing_loop INT := -1;
40 billing_row_count INT := 0;
41 current_billing_id BIGINT := 0;
42 billing_id_used BIGINT ARRAY;
44 continuing_payment BOOLEAN := FALSE;
45 continuing_payment_last_row BOOLEAN := FALSE;
48 /* We take a transaction id and fetch its payments in chronological order.
49 * We apply the payment amount, or a portion thereof, to each billing on
50 * the transaction, also in chronological order, until we run out of money
51 * from that payment. For each billing we encounter while we have money
52 * left from a payment we emmit a row of output containing the information
53 * about the billing and payment, and the amount of the current payment that
54 * was applied to the current billing.
57 -- First we'll go get the xact location. That will be the fallback location.
59 SELECT billing_location INTO billing_ou FROM money.grocery WHERE id = p_xact;
61 SELECT circ_lib INTO billing_ou FROM action.circulation WHERE id = p_xact;
64 SELECT count(id) INTO billing_row_count FROM money.billing WHERE xact = p_xact;
66 -- Loop through the positive payments
67 FOR current_payment IN
69 FROM money.payment_view
76 payment_remainder = current_payment.amount;
77 -- With every new payment row, we need to fast forward
78 -- the billing lines up to the last paid billing row
81 SELECT ws.owning_lib INTO payment_ou
82 FROM money.bnm_desk_payment p
83 JOIN actor.workstation ws ON (p.cash_drawer = ws.id)
84 WHERE p.id = current_payment.id;
85 -- If we don't do this then OPAC CC payments have no payment_ou
87 SELECT home_ou INTO payment_ou FROM actor.usr WHERE id = (SELECT accepting_usr FROM money.bnm_payment WHERE id = current_payment.id);
90 -- Were we looking at a billing from a previous step in the loop?
91 IF billing_remainder > 0.0 THEN
92 current_result.xact = p_xact;
93 current_result.payment = current_payment.id;
94 current_result.billing = current_billing.id;
95 current_result.payment_ts = current_payment.payment_ts;
96 current_result.billing_ts = current_billing.billing_ts;
97 current_result.payment_type = current_payment.payment_type;
98 current_result.billing_type = current_billing.billing_type;
99 current_result.payment_ou = payment_ou;
100 current_result.billing_ou = billing_ou;
102 IF billing_remainder >= payment_remainder THEN
103 current_result.amount = payment_remainder;
104 billing_remainder = billing_remainder - payment_remainder;
105 payment_remainder = 0.0;
106 payment_offset = payment_offset + 1;
107 -- If it is equal then we need to close up the billing line and move to the next
108 -- This prevents 0 amounts applied to billing lines
109 IF billing_remainder = payment_remainder THEN
110 billing_remainder = 0.0;
111 billing_offset = billing_offset + 1;
112 billing_id_used = array_append( billing_id_used, current_billing_id );
114 maintain_billing_remainder := TRUE;
118 current_result.amount = billing_remainder;
119 payment_remainder = payment_remainder - billing_remainder;
120 billing_remainder = 0.0;
121 billing_offset = billing_offset + 1;
122 billing_id_used = array_append( billing_id_used, current_billing_id );
123 continuing_payment := TRUE;
124 maintain_billing_remainder := FALSE;
127 RETURN NEXT current_result;
128 -- Done paying the billing rows when we run out of rows to pay (out of bounds)
129 EXIT WHEN array_length(billing_id_used, 1) = billing_row_count;
132 CONTINUE WHEN payment_remainder = 0.0;
134 -- next billing, please
137 FOR current_billing IN
141 -- Gotta put the voided billing rows at the bottom (last)
142 ORDER BY voided,billing_ts
144 billing_loop = billing_loop + 1;
146 -- Skip billing rows that we have already paid
147 IF billing_id_used @> ARRAY[current_billing.id] THEN CONTINUE;
150 IF maintain_billing_remainder THEN
151 CONTINUE WHEN current_billing.id <> current_billing_id;
152 -- Account adjustment - we expect to pay billing rows that are identical amounts
153 ELSE IF current_payment.payment_type = 'account_adjustment' THEN
154 -- Go ahead and allow the row through when it's the last row and we still haven't found one with equal payment amount
155 CONTINUE WHEN ( ( current_billing.amount <> current_payment.amount ) AND ( billing_loop + 1 <> billing_row_count ) );
159 -- Keep the old remainder if we were in the middle of a billing row
160 IF NOT maintain_billing_remainder THEN
161 billing_remainder = current_billing.amount;
164 maintain_billing_remainder := FALSE;
165 fast_forward := FALSE;
166 current_billing_id := current_billing.id;
167 continuing_payment := FALSE;
169 current_result.xact = p_xact;
170 current_result.payment = current_payment.id;
171 current_result.billing = current_billing.id;
172 current_result.payment_ts = current_payment.payment_ts;
173 current_result.billing_ts = current_billing.billing_ts;
174 current_result.payment_type = current_payment.payment_type;
175 current_result.billing_type = current_billing.billing_type;
176 current_result.payment_ou = payment_ou;
177 current_result.billing_ou = billing_ou;
179 IF billing_remainder >= payment_remainder THEN
180 current_result.amount = payment_remainder;
181 billing_remainder = billing_remainder - payment_remainder;
182 payment_remainder = 0.0;
183 -- If it is equal then we need to close up the billing line and move to the next
184 -- This prevents 0 amounts applied to billing lines
185 IF billing_remainder = payment_remainder THEN
186 billing_remainder = 0.0;
187 billing_offset = billing_offset + 1;
188 billing_id_used = array_append( billing_id_used, current_billing_id );
191 current_result.amount = billing_remainder;
192 payment_remainder = payment_remainder - billing_remainder;
193 continuing_payment := TRUE;
194 IF billing_loop + 1 = billing_row_count THEN
195 -- We have a situation where we are on the last billing row and we are in the middle of a payment row
196 -- We need to start back at the beginning of the billing rows and pay
197 continuing_payment_last_row := TRUE;
199 billing_remainder = 0.0;
200 billing_offset = billing_offset + 1;
201 billing_id_used = array_append( billing_id_used, current_billing_id );
204 RETURN NEXT current_result;
205 IF continuing_payment_last_row THEN
206 -- This should only occur when the account_adjustment's do not line up exactly with the billing
207 -- So we are going to pay some other type of billing row with this odd account_adjustment
208 -- And we need to stay in the current_payment row while doing so
210 FOR current_billing IN
214 ORDER BY voided,billing_ts
216 billing_loop = billing_loop + 1;
217 -- Skip billing rows that we have already paid
218 IF billing_id_used @> ARRAY[current_billing.id] THEN CONTINUE; END IF;
220 billing_remainder = current_billing.amount;
221 current_billing_id := current_billing.id;
222 continuing_payment := FALSE;
224 current_result.xact = p_xact;
225 current_result.payment = current_payment.id;
226 current_result.billing = current_billing.id;
227 current_result.payment_ts = current_payment.payment_ts;
228 current_result.billing_ts = current_billing.billing_ts;
229 current_result.payment_type = current_payment.payment_type;
230 current_result.billing_type = current_billing.billing_type;
231 current_result.payment_ou = payment_ou;
232 current_result.billing_ou = billing_ou;
234 IF billing_remainder >= payment_remainder THEN
235 current_result.amount = payment_remainder;
236 billing_remainder = billing_remainder - payment_remainder;
237 payment_remainder = 0.0;
238 -- If it is equal then we need to close up the billing line and move to the next
239 -- This prevents 0 amounts applied to billing lines
240 IF billing_remainder = payment_remainder THEN
241 billing_remainder = 0.0;
242 billing_offset = billing_offset + 1;
243 billing_id_used = array_append( billing_id_used, current_billing_id );
246 current_result.amount = billing_remainder;
247 payment_remainder = payment_remainder - billing_remainder;
248 billing_remainder = 0.0;
249 billing_offset = billing_offset + 1;
250 billing_id_used = array_append( billing_id_used, current_billing_id );
253 RETURN NEXT current_result;
254 EXIT WHEN payment_remainder = 0.0;
258 EXIT WHEN payment_remainder = 0.0;
261 payment_offset = payment_offset + 1;
262 -- Done paying the billing rows when we run out of rows to pay (out of bounds)
263 EXIT WHEN array_length(billing_id_used, 1) = billing_row_count;
267 payment_remainder := 0.0;
268 billing_remainder := 0.0;
271 billing_row_count := 0;
274 -- figure out how many voided billing rows there are
275 SELECT count(id) INTO billing_row_count FROM money.billing WHERE xact = p_xact AND voided;
277 -- Loop through the negative payments, these are refunds on voided billings
278 FOR current_payment IN
280 FROM money.payment_view
287 SELECT ws.owning_lib INTO payment_ou
288 FROM money.bnm_desk_payment p
289 JOIN actor.workstation ws ON (p.cash_drawer = ws.id)
290 WHERE p.id = current_payment.id;
293 SELECT home_ou INTO payment_ou FROM actor.usr WHERE id = (SELECT accepting_usr FROM money.bnm_payment WHERE id = current_payment.id);
296 payment_remainder = -current_payment.amount; -- invert
297 -- With every new payment row, we need to fast forward
298 -- the billing lines up to the last paid billing row
299 fast_forward := TRUE;
301 -- Were we looking at a billing from a previous step in the loop?
302 IF billing_remainder > 0.0 THEN
304 current_result.xact = p_xact;
305 current_result.payment = current_payment.id;
306 current_result.billing = current_billing.id;
307 current_result.payment_ts = current_payment.payment_ts;
308 current_result.billing_ts = current_billing.billing_ts;
309 current_result.payment_type = 'REFUND';
310 current_result.billing_type = current_billing.billing_type;
311 current_result.payment_ou = payment_ou;
312 current_result.billing_ou = billing_ou;
314 IF billing_remainder >= payment_remainder THEN
315 current_result.amount = payment_remainder;
316 billing_remainder = billing_remainder - payment_remainder;
317 payment_remainder = 0.0;
318 payment_offset = payment_offset + 1;
319 -- If it is equal then we need to close up the billing line and move to the next
320 -- This prevents 0 amounts applied to billing lines
321 IF billing_remainder = payment_remainder THEN
322 billing_remainder = 0.0;
323 billing_offset = billing_offset + 1;
325 maintain_billing_remainder := TRUE;
328 current_result.amount = billing_remainder;
329 payment_remainder = payment_remainder - billing_remainder;
330 billing_remainder = 0.0;
331 billing_offset = billing_offset + 1;
334 current_result.amount = -current_result.amount;
335 RETURN NEXT current_result;
336 -- Done paying the billing rows when we run out of rows to pay (out of bounds)
337 EXIT WHEN billing_offset = billing_row_count + 1;
340 CONTINUE WHEN payment_remainder = 0.0;
342 -- next billing, please
344 FOR current_billing IN
351 billing_loop = billing_loop + 1; -- first iteration billing_loop=0, it starts at -1
352 -- Fast forward through the rows until we get to the billing row
355 CONTINUE WHEN billing_loop <> billing_offset;
358 -- Keep the old remainder if we were in the middle of a billing row
359 IF NOT maintain_billing_remainder THEN
360 billing_remainder = current_billing.amount;
363 maintain_billing_remainder := FALSE;
364 fast_forward := FALSE;
366 current_result.xact = p_xact;
367 current_result.payment = current_payment.id;
368 current_result.billing = current_billing.id;
369 current_result.payment_ts = current_payment.payment_ts;
370 current_result.billing_ts = current_billing.billing_ts;
371 current_result.payment_type = 'REFUND';
372 current_result.billing_type = current_billing.billing_type;
373 current_result.payment_ou = payment_ou;
374 current_result.billing_ou = billing_ou;
376 IF billing_remainder >= payment_remainder THEN
377 current_result.amount = payment_remainder;
378 billing_remainder = billing_remainder - payment_remainder;
379 payment_remainder = 0.0;
380 -- If it is equal then we need to close up the billing line and move to the next
381 -- This prevents 0 amounts applied to billing lines
382 IF billing_remainder = payment_remainder THEN
383 billing_remainder = 0.0;
384 billing_offset = billing_offset + 1;
387 current_result.amount = billing_remainder;
388 payment_remainder = payment_remainder - billing_remainder;
389 billing_remainder = 0.0;
390 billing_offset = billing_offset + 1;
393 current_result.amount = -current_result.amount;
394 RETURN NEXT current_result;
396 EXIT WHEN payment_remainder = 0.0;
400 payment_offset = payment_offset + 1;
401 -- Done paying the billing rows when we run out of rows to pay (out of bounds)
402 EXIT WHEN billing_offset = billing_row_count + 1;
412 CREATE OR REPLACE FUNCTION money.payment_by_billing_type (
413 range_start TIMESTAMPTZ,
414 range_end TIMESTAMPTZ,
416 ) RETURNS SETOF money.materialized_payment_by_billing_type AS $$
419 current_transaction RECORD;
420 current_result money.materialized_payment_by_billing_type%ROWTYPE;
423 -- first, we find transactions at specified locations involving
424 -- positve, unvoided payments within the specified range
425 FOR current_transaction IN
427 FROM action.circulation x
428 JOIN money.payment p ON (x.id = p.xact)
429 JOIN actor.org_unit_descendants(location) d
430 ON (d.id = x.circ_lib)
431 WHERE p.payment_ts BETWEEN range_start AND range_end
437 JOIN money.payment p ON (x.id = p.xact)
438 JOIN actor.org_unit_descendants(location) d
439 ON (d.id = x.billing_location)
440 WHERE p.payment_ts BETWEEN range_start AND range_end
445 -- then, we send each transaction to the payment-by-billing-type
446 -- calculator, and return rows for payments we're interested in
447 FOR current_result IN
448 SELECT * FROM money.payment_by_billing_type( current_transaction.id )
450 IF current_result.payment_ts BETWEEN range_start AND range_end THEN
451 RETURN NEXT current_result;
461 CREATE OR REPLACE FUNCTION money.payment_by_billing_type_trigger ()
462 RETURNS TRIGGER AS $$
466 IF TG_OP = 'INSERT' THEN
467 DELETE FROM money.materialized_payment_by_billing_type
468 WHERE xact = NEW.xact;
470 INSERT INTO money.materialized_payment_by_billing_type (
471 xact, payment, billing, payment_ts, billing_ts,
472 payment_type, billing_type, amount, billing_ou, payment_ou
473 ) SELECT xact, payment, billing, payment_ts, billing_ts,
474 payment_type, billing_type, amount, billing_ou, payment_ou
475 FROM money.payment_by_billing_type( NEW.xact );
477 ELSIF TG_OP = 'UPDATE' THEN
478 DELETE FROM money.materialized_payment_by_billing_type
479 WHERE xact IN (OLD.xact,NEW.xact);
481 INSERT INTO money.materialized_payment_by_billing_type (
482 xact, payment, billing, payment_ts, billing_ts,
483 payment_type, billing_type, amount, billing_ou, payment_ou
484 ) SELECT xact, payment, billing, payment_ts, billing_ts,
485 payment_type, billing_type, amount, billing_ou, payment_ou
486 FROM money.payment_by_billing_type( NEW.xact );
488 IF NEW.xact <> OLD.xact THEN
489 INSERT INTO money.materialized_payment_by_billing_type (
490 xact, payment, billing, payment_ts, billing_ts,
491 payment_type, billing_type, amount, billing_ou, payment_ou
492 ) SELECT xact, payment, billing, payment_ts, billing_ts,
493 payment_type, billing_type, amount, billing_ou, payment_ou
494 FROM money.payment_by_billing_type( OLD.xact );
498 DELETE FROM money.materialized_payment_by_billing_type
499 WHERE xact = OLD.xact;
501 INSERT INTO money.materialized_payment_by_billing_type (
502 xact, payment, billing, payment_ts, billing_ts,
503 payment_type, billing_type, amount, billing_ou, payment_ou
504 ) SELECT xact, payment, billing, payment_ts, billing_ts,
505 payment_type, billing_type, amount, billing_ou, payment_ou
506 FROM money.payment_by_billing_type( OLD.xact );
518 CREATE TRIGGER calculate_payment_by_btype_tgr
519 AFTER INSERT OR UPDATE OR DELETE ON money.billing
520 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
522 CREATE TRIGGER calculate_payment_by_btype_tgr
523 AFTER INSERT OR UPDATE OR DELETE ON money.payment
524 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
526 CREATE TRIGGER calculate_payment_by_btype_tgr
527 AFTER INSERT OR UPDATE OR DELETE ON money.bnm_payment
528 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
530 CREATE TRIGGER calculate_payment_by_btype_tgr
531 AFTER INSERT OR UPDATE OR DELETE ON money.forgive_payment
532 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
534 CREATE TRIGGER calculate_payment_by_btype_tgr
535 AFTER INSERT OR UPDATE OR DELETE ON money.work_payment
536 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
538 CREATE TRIGGER calculate_payment_by_btype_tgr
539 AFTER INSERT OR UPDATE OR DELETE ON money.credit_payment
540 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
542 CREATE TRIGGER calculate_payment_by_btype_tgr
543 AFTER INSERT OR UPDATE OR DELETE ON money.goods_payment
544 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
546 CREATE TRIGGER calculate_payment_by_btype_tgr
547 AFTER INSERT OR UPDATE OR DELETE ON money.bnm_desk_payment
548 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
550 CREATE TRIGGER calculate_payment_by_btype_tgr
551 AFTER INSERT OR UPDATE OR DELETE ON money.cash_payment
552 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
554 CREATE TRIGGER calculate_payment_by_btype_tgr
555 AFTER INSERT OR UPDATE OR DELETE ON money.check_payment
556 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
558 CREATE TRIGGER calculate_payment_by_btype_tgr
559 AFTER INSERT OR UPDATE OR DELETE ON money.credit_card_payment
560 FOR EACH ROW EXECUTE PROCEDURE money.payment_by_billing_type_trigger();
566 -- Now Populate the materialized table
570 CREATE OR REPLACE FUNCTION tmp_populate_p_b_bt () RETURNS BOOL AS $$
579 INSERT INTO money.materialized_payment_by_billing_type (
580 xact, payment, billing, payment_ts, billing_ts,
581 payment_type, billing_type, amount, billing_ou, payment_ou
582 ) SELECT xact, payment, billing, payment_ts, billing_ts,
583 payment_type, billing_type, amount, billing_ou, payment_ou
584 FROM money.payment_by_billing_type( p.xact );
592 SELECT tmp_populate_p_b_bt();
594 DROP FUNCTION tmp_populate_p_b_bt ();