adding work_payment type and the /strong/ urging of PINES; extending the auth proxy...
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Storage / Publisher / money.pm
1 package OpenILS::Application::Storage::Publisher::money;
2 use base qw/OpenILS::Application::Storage/;
3 use OpenSRF::Utils::Logger qw/:level/;
4
5 my $log = 'OpenSRF::Utils::Logger';
6
7 sub _make_mbts {
8         my @xacts = @_;
9
10         my @mbts;
11         for my $x (@xacts) {
12                 my $s = new Fieldmapper::money::billable_transaction_summary;
13                 $s->id( $x->id );
14                 $s->usr( $x->usr );
15                 $s->xact_start( $x->xact_start );
16                 $s->xact_finish( $x->xact_finish );
17
18                 my $to = 0;
19                 my $lb = undef;
20                 for my $b ($x->billings) {
21                         next if ($b->voided);
22                         #$log->debug( "billing is ".$b->amount, DEBUG );
23                         $to += ($b->amount * 100);
24                         $lb ||= $b->billing_ts;
25                         if ($b->billing_ts ge $lb) {
26                                 $lb = $b->billing_ts;
27                                 $s->last_billing_note($b->note);
28                                 $s->last_billing_ts($b->billing_ts);
29                                 $s->last_billing_type($b->billing_type);
30                         }
31                 }
32
33                 $s->total_owed( sprintf('%0.2f', ($to) / 100 ) );
34
35                 my $tp = 0;
36                 my $lp = undef;
37                 for my $p ($x->payments) {
38                         #$log->debug( "payment is ".$p->amount." voided = ".$p->voided, DEBUG );
39                         next if ($p->voided eq 't');
40                         $tp += ($p->amount * 100);
41                         $lp ||= $p->payment_ts;
42                         if ($p->payment_ts ge $lp) {
43                                 $lp = $p->payment_ts;
44                                 $s->last_payment_note($p->note);
45                                 $s->last_payment_ts($p->payment_ts);
46                                 $s->last_payment_type($p->payment_type);
47                         }
48                 }
49                 $s->total_paid( sprintf('%0.2f', ($tp) / 100 ) );
50
51                 $s->balance_owed( sprintf('%0.2f', (($to) - ($tp)) / 100) );
52                 #$log->debug( "balance of ".$x->id." == ".$s->balance_owed, DEBUG );
53
54                 $s->xact_type( 'grocery' ) if (money::grocery->retrieve($x->id));
55                 $s->xact_type( 'circulation' ) if (action::circulation->retrieve($x->id));
56
57                 push @mbts, $s;
58         }
59
60         return @mbts;
61 }
62
63 sub search_mbts {
64         my $self = shift;
65         my $client = shift;
66         my $search = shift;
67
68         my @xacts = money::billable_transaction->search_where( $search );
69         $client->respond( $_ ) for (_make_mbts(@xacts));
70
71         return undef;
72 }
73 __PACKAGE__->register_method(
74         method          => 'search_mbts',
75         api_name        => 'open-ils.storage.money.billable_transaction.summary.search',
76         stream          => 1,
77         argc            => 1,
78 );
79
80 sub search_ous {
81         my $self = shift;
82         my $client = shift;
83         my $usr = shift;
84
85         my @xacts = $self->method_lookup( 'open-ils.storage.money.billable_transaction.summary.search' )->run( { usr => $usr, xact_finish => undef } );
86
87         my ($total,$owed,$paid) = (0.0,0.0,0.0);
88         for my $x (@xacts) {
89                 $total += $x->total_owed;
90                 $owed += $x->balance_owed;
91                 $paid += $x->total_paid;
92         }
93
94         my $ous = Fieldmapper::money::open_user_summary->new;
95         $ous->usr( $usr );
96         $ous->total_paid( sprintf('%0.2f', $paid) );
97         $ous->total_owed( sprintf('%0.2f', $total) );
98         $ous->balance_owed( sprintf('%0.2f', $owed) );
99
100         return $ous;
101 }
102 __PACKAGE__->register_method(
103         method          => 'search_ous',
104         api_name        => 'open-ils.storage.money.open_user_summary.search',
105         argc            => 1,
106 );
107
108
109 sub new_collections {
110         my $self = shift;
111         my $client = shift;
112         my $age = shift;
113         my $amount = shift;
114         my @loc = @_;
115
116         my $mct = money::collections_tracker->table;
117         my $mb = money::billing->table;
118         my $circ = action::circulation->table;
119         my $mg = money::grocery->table;
120         my $descendants = "actor.org_unit_descendants((select id from actor.org_unit where shortname = ?))";
121
122         my $SQL = <<"   SQL";
123
124 select
125         usr,
126         MAX(last_billing) as last_pertinent_billing,
127         SUM(total_billing) - SUM(COALESCE(p.amount,0)) as threshold_amount
128   from  (select
129                 x.id,
130                 x.usr,
131                 MAX(b.billing_ts) as last_billing,
132                 SUM(b.amount) AS total_billing
133           from  action.circulation x
134                 left join money.collections_tracker c ON (c.usr = x.usr AND c.location = ?)
135                 join money.billing b on (b.xact = x.id)
136           where x.xact_finish is null
137                 and c.id is null
138                 and x.circ_lib in (XX)
139                 and b.billing_ts < current_timestamp - ? * '1 day'::interval
140                 and not b.voided
141           group by 1,2
142
143                   union all
144
145          select
146                 x.id,
147                 x.usr,
148                 MAX(b.billing_ts) as last_billing,
149                 SUM(b.amount) AS total_billing
150           from  money.grocery x
151                 left join money.collections_tracker c ON (c.usr = x.usr AND c.location = ?)
152                 join money.billing b on (b.xact = x.id)
153           where x.xact_finish is null
154                 and c.id is null
155                 and x.billing_location in (XX)
156                 and b.billing_ts < current_timestamp - ? * '1 day'::interval
157                 and not b.voided
158           group by 1,2
159         ) full_list
160         left join money.payment p on (full_list.id = p.xact)
161   group by 1
162   having SUM(total_billing) - SUM(COALESCE(p.amount,0)) > ?
163 ;
164         SQL
165
166         my @l_ids;
167         for my $l (@loc) {
168                 my ($org) = actor::org_unit->search( shortname => uc($l) );
169                 next unless $org;
170
171                 my $o_list = actor::org_unit->db_Main->selectcol_arrayref( "SELECT id FROM actor.org_unit_descendants(?);", {}, $org->id );
172                 next unless (@$o_list);
173
174                 my $o_txt = join ',' => @$o_list;
175
176                 (my $real_sql = $SQL) =~ s/XX/$o_txt/gsm;
177
178                 my $sth = money::collections_tracker->db_Main->prepare($real_sql);
179                 $sth->execute( $org->id, $age, $org->id, $age, $amount );
180
181                 while (my $row = $sth->fetchrow_hashref) {
182                         #$row->{usr} = actor::user->retrieve($row->{usr})->to_fieldmapper;
183                         $client->respond( $row );
184                 }
185         }
186         return undef;
187 }
188 __PACKAGE__->register_method(
189         method          => 'new_collections',
190         api_name        => 'open-ils.storage.money.collections.users_of_interest',
191         stream          => 1,
192         argc            => 3,
193 );
194
195 sub users_owing_money {
196         my $self = shift;
197         my $client = shift;
198         my $start = shift;
199         my $end = shift;
200         my $amount = shift;
201         my @loc = @_;
202
203         my $mct = money::collections_tracker->table;
204         my $mb = money::billing->table;
205         my $circ = action::circulation->table;
206         my $mg = money::grocery->table;
207         my $descendants = "actor.org_unit_descendants((select id from actor.org_unit where shortname = ?))";
208
209         my $SQL = <<"   SQL";
210
211 select
212         usr,
213         SUM(total_billing) - SUM(COALESCE(p.amount,0)) as threshold_amount
214   from  (select
215                 x.id,
216                 x.usr,
217                 SUM(b.amount) AS total_billing
218           from  action.circulation x
219                 join money.billing b on (b.xact = x.id)
220           where x.xact_finish is null
221                 and x.circ_lib in (XX)
222                 and b.billing_ts between ? and ?
223                 and not b.voided
224           group by 1,2
225
226                   union all
227
228          select
229                 x.id,
230                 x.usr,
231                 SUM(b.amount) AS total_billing
232           from  money.grocery x
233                 join money.billing b on (b.xact = x.id)
234           where x.xact_finish is null
235                 and x.billing_location in (XX)
236                 and b.billing_ts between ? and ?
237                 and not b.voided
238           group by 1,2
239         ) full_list
240         left join money.payment p on (full_list.id = p.xact)
241   group by 1
242   having SUM(total_billing) - SUM(COALESCE(p.amount,0)) > ?
243 ;
244         SQL
245
246         my @l_ids;
247         for my $l (@loc) {
248                 my ($org) = actor::org_unit->search( shortname => uc($l) );
249                 next unless $org;
250
251                 my $o_list = actor::org_unit->db_Main->selectcol_arrayref( "SELECT id FROM actor.org_unit_descendants(?);", {}, $org->id );
252                 next unless (@$o_list);
253
254                 my $o_txt = join ',' => @$o_list;
255
256                 (my $real_sql = $SQL) =~ s/XX/$o_txt/gsm;
257
258                 my $sth = money::collections_tracker->db_Main->prepare($real_sql);
259                 $sth->execute( $start, $end, $start, $end, $amount );
260
261                 while (my $row = $sth->fetchrow_hashref) {
262                         #$row->{usr} = actor::user->retrieve($row->{usr})->to_fieldmapper;
263                         $client->respond( $row );
264                 }
265         }
266         return undef;
267 }
268 __PACKAGE__->register_method(
269         method          => 'users_owing_money',
270         api_name        => 'open-ils.storage.money.collections.users_owing_money',
271         stream          => 1,
272         argc            => 4,
273 );
274
275 sub active_in_collections {
276         my $self = shift;
277         my $client = shift;
278         my $startdate = shift;
279         my $enddate = shift;
280         my @loc = @_;
281
282         my $mct = money::collections_tracker->table;
283         my $mb = money::billing->table;
284         my $circ = action::circulation->table;
285         my $mg = money::grocery->table;
286
287         my $SQL = <<"   SQL";
288 SELECT  usr,
289         MAX(last_pertinent_billing) AS last_pertinent_billing,
290         MAX(last_pertinent_payment) AS last_pertinent_payment
291   FROM  (
292                 SELECT  lt.usr,
293                         NULL::TIMESTAMPTZ AS last_pertinent_billing,
294                         NULL::TIMESTAMPTZ AS last_pertinent_payment
295                   FROM  money.grocery lt
296                         JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
297                         JOIN money.billing bl ON (lt.id = bl.xact)
298                   WHERE cl.location = ?
299                         AND lt.billing_location IN (XX)
300                         AND bl.void_time BETWEEN ? AND ?
301                   GROUP BY 1
302
303                                 UNION ALL
304                 SELECT  lt.usr,
305                         MAX(bl.billing_ts) AS last_pertinent_billing,
306                         NULL::TIMESTAMPTZ AS last_pertinent_payment
307                   FROM  money.grocery lt
308                         JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
309                         JOIN money.billing bl ON (lt.id = bl.xact)
310                   WHERE cl.location = ?
311                         AND lt.billing_location IN (XX)
312                         AND bl.billing_ts BETWEEN ? AND ?
313                   GROUP BY 1
314
315                                 UNION ALL
316                 SELECT  lt.usr,
317                         NULL::TIMESTAMPTZ AS last_pertinent_billing,
318                         MAX(pm.payment_ts) AS last_pertinent_payment
319                   FROM  money.grocery lt
320                         JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
321                         JOIN money.payment pm ON (lt.id = pm.xact)
322                   WHERE cl.location = ?
323                         AND lt.billing_location IN (XX)
324                         AND pm.payment_ts BETWEEN ? AND ?
325                   GROUP BY 1
326
327                                 UNION ALL
328                 SELECT  lt.usr,
329                         NULL::TIMESTAMPTZ AS last_pertinent_billing,
330                         NULL::TIMESTAMPTZ AS last_pertinent_payment
331                   FROM  action.circulation lt
332                         JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
333                   WHERE cl.location = ?
334                         AND lt.circ_lib IN (XX)
335                         AND lt.checkin_time BETWEEN ? AND ?
336                   GROUP BY 1
337
338                                 UNION ALL
339                 SELECT  lt.usr,
340                         NULL::TIMESTAMPTZ AS last_pertinent_billing,
341                         MAX(pm.payment_ts) AS last_pertinent_payment
342                   FROM  action.circulation lt
343                         JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
344                         JOIN money.payment pm ON (lt.id = pm.xact)
345                   WHERE cl.location = ?
346                         AND lt.circ_lib IN (XX)
347                         AND pm.payment_ts BETWEEN ? AND ?
348                   GROUP BY 1
349
350                                 UNION ALL
351                 SELECT  lt.usr,
352                         NULL::TIMESTAMPTZ AS last_pertinent_billing,
353                         NULL::TIMESTAMPTZ AS last_pertinent_payment
354                   FROM  action.circulation lt
355                         JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
356                         JOIN money.billing bl ON (lt.id = bl.xact)
357                   WHERE cl.location = ?
358                         AND lt.circ_lib IN (XX)
359                         AND bl.void_time BETWEEN ? AND ?
360                   GROUP BY 1
361
362                                 UNION ALL
363                 SELECT  lt.usr,
364                         MAX(bl.billing_ts) AS last_pertinent_billing,
365                         NULL::TIMESTAMPTZ AS last_pertinent_payment
366                   FROM  action.circulation lt
367                         JOIN money.collections_tracker cl ON (lt.usr = cl.usr)
368                         JOIN money.billing bl ON (lt.id = bl.xact)
369                   WHERE cl.location = ?
370                         AND lt.circ_lib IN (XX)
371                         AND bl.billing_ts BETWEEN ? AND ?
372                   GROUP BY 1
373         ) foo
374   GROUP BY 1
375 ;
376         SQL
377
378         my @l_ids;
379         for my $l (@loc) {
380                 my ($org) = actor::org_unit->search( shortname => uc($l) );
381                 next unless $org;
382
383                 my $o_list = actor::org_unit->db_Main->selectcol_arrayref( "SELECT id FROM actor.org_unit_descendants(?);", {}, $org->id );
384                 next unless (@$o_list);
385
386                 my $o_txt = join ',' => @$o_list;
387
388                 (my $real_sql = $SQL) =~ s/XX/$o_txt/gsm;
389
390                 my $sth = money::collections_tracker->db_Main->prepare($real_sql);
391                 $sth->execute(
392                         $org->id, $startdate, $enddate,
393                         $org->id, $startdate, $enddate,
394                         $org->id, $startdate, $enddate,
395                         $org->id, $startdate, $enddate,
396                         $org->id, $startdate, $enddate
397                 );
398
399                 while (my $row = $sth->fetchrow_hashref) {
400                         $row->{usr} = actor::user->retrieve($row->{usr})->to_fieldmapper;
401                         $client->respond( $row );
402                 }
403         }
404         return undef;
405 }
406 __PACKAGE__->register_method(
407         method          => 'active_in_collections',
408         api_name        => 'open-ils.storage.money.collections.users_with_activity',
409         stream          => 1,
410         argc            => 3,
411 );
412
413 sub ou_desk_payments {
414         my $self = shift;
415         my $client = shift;
416         my $lib = shift;
417         my $startdate = shift;
418         my $enddate = shift;
419
420         return undef unless ($startdate =~ /^\d{4}-\d{2}-\d{2}$/o);
421         return undef unless ($enddate =~ /^\d{4}-\d{2}-\d{2}$/o);
422         return undef unless ($lib =~ /^\d+$/o);
423
424         my $sql = <<"   SQL";
425
426         SELECT  ws.id as workstation,
427                 SUM( CASE WHEN p.payment_type = 'cash_payment' THEN p.amount ELSE 0.0 END ) as cash_payment,
428                 SUM( CASE WHEN p.payment_type = 'check_payment' THEN p.amount ELSE 0.0 END ) as check_payment,
429                 SUM( CASE WHEN p.payment_type = 'credit_card_payment' THEN p.amount ELSE 0.0 END ) as credit_card_payment
430           FROM  money.desk_payment_view p
431                 JOIN actor.workstation ws ON (ws.id = p.cash_drawer)
432           WHERE p.payment_ts >= '$startdate'
433                 AND p.payment_ts < '$enddate'::TIMESTAMPTZ + INTERVAL '1 day'
434                 AND p.voided IS FALSE
435                 AND ws.owning_lib = $lib
436          GROUP BY 1
437          ORDER BY 1;
438
439         SQL
440
441         my $rows = money::payment->db_Main->selectall_arrayref( $sql );
442
443         for my $r (@$rows) {
444                 my $x = new Fieldmapper::money::workstation_payment_summary;
445                 $x->workstation( actor::workstation->retrieve($$r[0])->to_fieldmapper );
446                 $x->cash_payment($$r[1]);
447                 $x->check_payment($$r[2]);
448                 $x->credit_card_payment($$r[3]);
449
450                 $client->respond($x);
451         }
452
453         return undef;
454 }
455 __PACKAGE__->register_method(
456         method          => 'ou_desk_payments',
457         api_name        => 'open-ils.storage.money.org_unit.desk_payments',
458         stream          => 1,
459         argc            => 3,
460 );
461
462 sub ou_user_payments {
463         my $self = shift;
464         my $client = shift;
465         my $lib = shift;
466         my $startdate = shift;
467         my $enddate = shift;
468
469         return undef unless ($startdate =~ /^\d{4}-\d{2}-\d{2}$/o);
470         return undef unless ($enddate =~ /^\d{4}-\d{2}-\d{2}$/o);
471         return undef unless ($lib =~ /^\d+$/o);
472
473         my $sql = <<"   SQL";
474
475         SELECT  au.id as usr,
476                 SUM( CASE WHEN p.payment_type = 'forgive_payment' THEN p.amount ELSE 0.0 END ) as forgive_payment,
477                 SUM( CASE WHEN p.payment_type = 'work_payment' THEN p.amount ELSE 0.0 END ) as work_payment,
478                 SUM( CASE WHEN p.payment_type = 'credit_payment' THEN p.amount ELSE 0.0 END ) as credit_payment,
479                 SUM( CASE WHEN p.payment_type = 'goods_payment' THEN p.amount ELSE 0.0 END ) as goods_payment
480           FROM  money.bnm_payment_view p
481                 JOIN actor.usr au ON (au.id = p.accepting_usr)
482           WHERE p.payment_ts >= '$startdate'
483                 AND p.payment_ts < '$enddate'::TIMESTAMPTZ + INTERVAL '1 day'
484                 AND p.voided IS FALSE
485                 AND au.home_ou = $lib
486                 AND p.payment_type IN ('credit_payment','forgive_payment','work_payment','goods_payment')
487          GROUP BY 1
488          ORDER BY 1;
489
490         SQL
491
492         my $rows = money::payment->db_Main->selectall_arrayref( $sql );
493
494         for my $r (@$rows) {
495                 my $x = new Fieldmapper::money::user_payment_summary;
496                 $x->usr( actor::user->retrieve($$r[0])->to_fieldmapper );
497                 $x->forgive_payment($$r[1]);
498                 $x->work_payment($$r[2]);
499                 $x->credit_payment($$r[3]);
500                 $x->goods_payment($$r[4]);
501
502                 $client->respond($x);
503         }
504
505         return undef;
506 }
507 __PACKAGE__->register_method(
508         method          => 'ou_user_payments',
509         api_name        => 'open-ils.storage.money.org_unit.user_payments',
510         stream          => 1,
511         argc            => 3,
512 );
513
514
515 1;