]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm
Acq: Make the distribution formula UI page like it was supposed to
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Acq / Picklist.pm
1 package OpenILS::Application::Acq::Picklist;
2 use base qw/OpenILS::Application/;
3 use strict; use warnings;
4
5 use OpenSRF::EX q/:try/;
6 use OpenSRF::Utils::Logger qw(:logger);
7 use OpenILS::Utils::Fieldmapper;
8 use OpenILS::Utils::CStoreEditor q/:funcs/;
9 use OpenILS::Const qw/:const/;
10 use OpenSRF::Utils::SettingsClient;
11 use OpenILS::Event;
12 use OpenILS::Application::AppUtils;
13 use OpenSRF::Utils::Cache;
14 use MARC::Record;
15 use MARC::Batch;
16 use MARC::File::XML;
17 use MIME::Base64;
18 use Digest::MD5 qw/md5_hex/;
19 use OpenILS::Application::Acq::Financials;
20 use DateTime;
21
22 my $U = 'OpenILS::Application::AppUtils';
23
24
25 __PACKAGE__->register_method(
26         method => 'create_picklist',
27         api_name        => 'open-ils.acq.picklist.create',
28         signature => {
29         desc => 'Creates a new picklist',
30         params => [
31             {desc => 'Authentication token', type => 'string'},
32             {desc => 'Picklist object to create', type => 'object'}
33         ],
34         return => {desc => 'The ID of the new picklist'}
35     }
36 );
37
38 sub create_picklist {
39     my($self, $conn, $auth, $picklist) = @_;
40     my $e = new_editor(xact=>1, authtoken=>$auth);
41     return $e->die_event unless $e->checkauth;
42     $picklist->creator($e->requestor->id);
43     $picklist->editor($e->requestor->id);
44     $picklist->org_unit($e->requestor->ws_ou) unless $picklist->org_unit;
45     return $e->die_event unless $e->allowed('CREATE_PICKLIST', $picklist->org_unit);
46     return OpenILS::Event->new('BAD_PARAMS')
47         unless $e->requestor->id == $picklist->owner;
48     $e->create_acq_picklist($picklist) or return $e->die_event;
49     $e->commit;
50     return $picklist->id;
51 }
52
53
54 __PACKAGE__->register_method(
55         method => 'update_picklist',
56         api_name        => 'open-ils.acq.picklist.update',
57         signature => {
58         desc => 'Updates a new picklist',
59         params => [
60             {desc => 'Authentication token', type => 'string'},
61             {desc => 'Picklist object to update', type => 'object'}
62         ],
63         return => {desc => '1 on success, Event on error'}
64     }
65 );
66
67 sub update_picklist {
68     my($self, $conn, $auth, $picklist) = @_;
69     my $e = new_editor(xact=>1, authtoken=>$auth);
70     return $e->die_event unless $e->checkauth;
71
72     # don't let them change the owner
73     my $o_picklist = $e->retrieve_acq_picklist($picklist->id)
74         or return $e->die_event;
75     if($o_picklist->owner != $e->requestor->id) {
76         return $e->die_event unless 
77             $e->allowed('UPDATE_PICKLIST', $o_picklist->org_unit);
78     }
79     return OpenILS::Event->new('BAD_PARAMS') unless $o_picklist->org_unit == $picklist->org_unit;
80
81     $picklist->edit_time('now');
82     $picklist->editor($e->requestor->id);
83     $e->update_acq_picklist($picklist) or return $e->die_event;
84     $e->commit;
85     return 1;
86 }
87
88 __PACKAGE__->register_method(
89         method => 'retrieve_picklist',
90         api_name        => 'open-ils.acq.picklist.retrieve',
91         signature => {
92         desc => 'Retrieves a picklist',
93         params => [
94             {desc => 'Authentication token', type => 'string'},
95             {desc => 'Picklist ID to retrieve', type => 'number'},
96             {desc => 'Options hash, including "flesh_lineitem_count" to get the count of attached entries', type => 'hash'},
97         ],
98         return => {desc => 'Picklist object on success, Event on error'}
99     }
100 );
101
102 sub retrieve_picklist {
103     my($self, $conn, $auth, $picklist_id, $options) = @_;
104     my $e = new_editor(authtoken=>$auth);
105     return $e->event unless $e->checkauth;
106
107     return retrieve_picklist_impl($e, $picklist_id, $options);
108 }
109
110 sub retrieve_picklist_impl {
111     my ($e, $picklist_id, $options) = @_;
112     $options ||= {};
113
114     my $picklist = $e->retrieve_acq_picklist($picklist_id)
115         or return $e->event;
116
117     $picklist->entry_count(retrieve_lineitem_count($e, $picklist_id))
118         if $$options{flesh_lineitem_count};
119
120     if($e->requestor->id != $picklist->owner) {
121         return $e->event unless 
122             $e->allowed('VIEW_PICKLIST', $picklist->org_unit, $picklist);
123     }
124
125     $picklist->owner($e->retrieve_actor_user($picklist->owner)) 
126         if($$options{flesh_owner});
127     $picklist->owner($e->retrieve_actor_user($picklist->owner)->usrname) 
128         if($$options{flesh_username});
129
130     return $picklist;
131 }
132
133
134 # Returns the number of entries associated with this picklist
135 sub retrieve_lineitem_count {
136     my($e, $picklist_id) = @_;
137     my $count = $e->json_query({
138         select => { 
139             jub => [{transform => 'count', column => 'id', alias => 'count'}]
140         }, 
141         from => 'jub', 
142         where => {picklist => $picklist_id}}
143     );
144     return $count->[0]->{count};
145 }
146
147
148
149 __PACKAGE__->register_method(
150         method => 'retrieve_picklist_name',
151         api_name        => 'open-ils.acq.picklist.name.retrieve',
152         signature => {
153         desc => 'Retrieves a picklist by name.  Owner is implied by the caller',
154         params => [
155             {desc => 'Authentication token',      type => 'string'},
156             {desc => 'Picklist name to retrieve', type => 'string'},
157         ],
158         return => {desc => 'Picklist object on success, null on not found'}
159     }
160 );
161
162 sub retrieve_picklist_name {
163     my($self, $conn, $auth, $name) = @_;
164     my $e = new_editor(authtoken=>$auth);
165     return $e->event unless $e->checkauth;
166     my $picklist = $e->search_acq_picklist(
167         {name => $name, owner => $e->requestor->id})->[0];
168     if($e->requestor->id != $picklist->owner) {
169         return $e->event unless 
170             $e->allowed('VIEW_PICKLIST', $picklist->org_unit, $picklist);
171     }
172     return $picklist;
173 }
174
175
176
177 __PACKAGE__->register_method(
178         method => 'retrieve_user_picklist',
179         api_name        => 'open-ils.acq.picklist.user.retrieve',
180     stream => 1,
181         signature => {
182         desc => 'Retrieves a  user\'s picklists',
183         params => [
184             {desc => 'Authentication token', type => 'string'},
185             {desc => 'Options, including "idlist", whch forces the return
186                 of a list of IDs instead of objects', type => 'hash'},
187         ],
188         return => {desc => 'Picklist object on success, Event on error'}
189     }
190 );
191
192 sub retrieve_user_picklist {
193     my($self, $conn, $auth, $options) = @_;
194     my $e = new_editor(authtoken=>$auth);
195     return $e->die_event unless $e->checkauth;
196     $options ||= {};
197
198     # don't grab the PL with name == "", because that is the designated temporary picklist
199     my $list = $e->search_acq_picklist([
200             {
201                 owner => $e->requestor->id, 
202                 name => {'!=' => ''}
203             }, {
204                 order_by => $$options{order_by} || {acqpl => 'edit_time DESC'},
205                 limit => $$options{limit} || 10,
206                 offset => $$options{offset} || 0,
207             }
208         ],
209         {idlist=>1}
210     );
211
212     for my $id (@$list) {
213         if($$options{idlist}) {
214             $conn->respond($id);
215         } else {
216             my $pl = $e->retrieve_acq_picklist($id);
217             $pl->entry_count(retrieve_lineitem_count($e, $id)) if $$options{flesh_lineitem_count};
218             $pl->owner($e->retrieve_actor_user($pl->owner)) if $$options{flesh_owner};
219             $pl->owner($e->retrieve_actor_user($pl->owner)->usrname) if $$options{flesh_username};
220             $conn->respond($pl);
221         }
222     }
223
224     return undef;
225 }
226
227
228 __PACKAGE__->register_method(
229         method => 'retrieve_all_user_picklist',
230         api_name        => 'open-ils.acq.picklist.user.all.retrieve',
231     stream => 1,
232         signature => {
233         desc => 'Retrieves all of the picklists a user is allowed to see',
234         params => [
235             {desc => 'Authentication token', type => 'string'},
236             {desc => 'Options, including "idlist", whch forces the return
237                 of a list of IDs instead of objects', type => 'hash'},
238         ],
239         return => {desc => 'Picklist objects on success, Event on error'}
240     }
241 );
242
243 sub retrieve_all_user_picklist {
244     my($self, $conn, $auth, $options) = @_;
245     my $e = new_editor(authtoken=>$auth);
246     return $e->event unless $e->checkauth;
247
248     my $my_list = $e->search_acq_picklist(
249         {owner=>$e->requestor->id, name=>{'!='=>''}}, {idlist=>1});
250
251     my $picklist_ids = $e->objects_allowed('VIEW_PICKLIST', 'acqpl');
252     my $p_orgs = $U->user_has_work_perm_at($e, 'VIEW_PICKLIST', {descendants =>1});
253     my $picklist_ids_2 = $e->search_acq_picklist(
254         {name=>{'!='=>''}, org_unit => $p_orgs}, {idlist=>1});
255
256     return undef unless @$my_list or @$picklist_ids or @$picklist_ids_2;
257
258     my @list = (@$my_list, @$picklist_ids, @$picklist_ids_2);
259     my %dedup;
260     $dedup{$_} = 1 for @list;
261     @list = keys %dedup;
262
263     return \@list if $$options{idlist};
264
265     for my $pl (@list) {
266         my $picklist = $e->retrieve_acq_picklist($pl) or return $e->event;
267         $picklist->entry_count(retrieve_lineitem_count($e, $picklist->id))
268             if($$options{flesh_lineitem_count});
269         $picklist->owner($e->retrieve_actor_user($picklist->owner))
270             if $$options{flesh_owner};
271         $picklist->owner($e->retrieve_actor_user($picklist->owner)->usrname)
272             if $$options{flesh_username};
273         $conn->respond($picklist);
274     }
275
276     return undef;
277 }
278
279 __PACKAGE__->register_method(
280         method => 'retrieve_pl_lineitem',
281         api_name        => 'open-ils.acq.lineitem.picklist.retrieve',
282     stream => 1,
283         signature => {
284         desc => 'Retrieves lineitem objects according to picklist',
285         params => [
286             {desc => 'Authentication token', type => 'string'},
287             {desc => 'Picklist ID whose entries to retrieve', type => 'number'},
288             {desc => q/Options, including 
289                 "sort_attr", which defines the attribute to sort on; 
290                 "sort_attr_type", which defines the attribute type sort on; 
291                 "sort_dir", which defines the sort order between "asc" and "desc";
292                 "limit", retrieval limit;
293                 "offset", retrieval offset;
294                 "idlist", return a list of IDs instead of objects
295                 "flesh_attrs", additionaly return the list of flattened attributes
296                 "clear_marc", discards the raw MARC data to reduce data size
297                 "flesh_notes", flesh lineitem notes
298                 "flesh_cancel_reason", flesh cancel_reason
299                 /, 
300                 type => 'hash'}
301         ],
302         return => {desc => 'Array of lineitem objects or IDs,  on success, Event on error'}
303     }
304 );
305
306
307 my $PL_ENTRY_JSON_QUERY = {
308     select => {jub => ["id"], "acqlia" => ["attr_value"]},
309     "from" => {
310         "jub" => {
311             "acqlia" => {
312                 "fkey" => "id", 
313                 "field" => "lineitem", 
314                 "type" => "left", 
315                 "filter" => {
316                     "attr_type" => "lineitem_marc_attr_definition", 
317                     "attr_name" => "author" 
318                 }
319             }
320         }
321     }, 
322     "order_by" => {"acqlia" => {"attr_value" => {"direction"=>"asc"}}}, 
323     "limit" => 10,
324     "where" => {"+jub" => {"picklist"=>2}},
325     "offset" => 0
326 };
327
328 sub retrieve_pl_lineitem {
329     my($self, $conn, $auth, $picklist_id, $options) = @_;
330     my $e = new_editor(authtoken=>$auth);
331     return $e->event unless $e->checkauth;
332
333     # collect the retrieval options
334     my $sort_attr = $$options{sort_attr} || 'title';
335     my $sort_attr_type = $$options{sort_attr_type} || 'lineitem_marc_attr_definition';
336     my $sort_dir = $$options{sort_dir} || 'asc';
337     my $limit = $$options{limit} || 10;
338     my $offset = $$options{offset} || 0;
339
340     $PL_ENTRY_JSON_QUERY->{where}->{'+jub'}->{picklist} = $picklist_id;
341     $PL_ENTRY_JSON_QUERY->{from}->{jub}->{acqlia}->{filter}->{attr_name} = $sort_attr;
342     $PL_ENTRY_JSON_QUERY->{from}->{jub}->{acqlia}->{filter}->{attr_type} = $sort_attr_type;
343     $PL_ENTRY_JSON_QUERY->{order_by}->{acqlia}->{attr_value}->{direction} = $sort_dir;
344     $PL_ENTRY_JSON_QUERY->{limit} = $limit;
345     $PL_ENTRY_JSON_QUERY->{offset} = $offset;
346
347     my $entries = $e->json_query($PL_ENTRY_JSON_QUERY);
348
349     my @ids;
350     for my $entry (@$entries) {
351         push(@ids, $entry->{id}) unless grep { $_ eq $entry->{id} } @ids;
352     }
353
354     for my $id (@ids) {
355         if($$options{idlist}) {
356             $conn->respond($id);
357             next;
358         } 
359
360         my $entry;
361         my $flesh = {};
362         if($$options{flesh_attrs} or $$options{flesh_notes} or $$options{flesh_cancel_reason}) {
363             $flesh = {flesh => 2, flesh_fields => {jub => []}};
364             if($$options{flesh_notes}) {
365                 push(@{$flesh->{flesh_fields}->{jub}}, 'lineitem_notes');
366                 $flesh->{flesh_fields}->{acqlin} = ['alert_text'];
367             }
368             push(@{$flesh->{flesh_fields}->{jub}}, 'attributes') if $$options{flesh_attrs};
369             push @{$flesh->{flesh_fields}->{jub}}, 'cancel_reason' if $$options{flesh_cancel_reason};
370         }
371
372         $entry = $e->retrieve_acq_lineitem([$id, $flesh]);
373         my $details = $e->search_acq_lineitem_detail({lineitem => $id}, {idlist=>1});
374         $entry->item_count(scalar(@$details));
375         $entry->clear_marc if $$options{clear_marc};
376         $conn->respond($entry);
377     }
378
379     return undef;
380 }
381
382 =head comment
383 request open-ils.cstore open-ils.cstore.json_query.atomic {"select":{"jub":[{"transform":"count", "attregate":1, "column":"id","alias":"count"}]}, "from":"jub","where":{"picklist":1}}
384 =cut
385
386
387
388 __PACKAGE__->register_method(
389     method    => "record_distribution_formula_application",
390     api_name  => "open-ils.acq.distribution_formula.record_application",
391     signature => {
392         desc  => "Record the application (which actually happens on the " .
393             "client side) of a distribution formula to a PO or a PL",
394         params => [
395             {desc => "Authentication token", type => "string"},
396             {desc => "Formulae applied", "type" => "array"},
397             {desc => "Lineitem ID", "type" => "number"}
398         ],
399         return => {desc => "acqdfa IDs on success; event on failure"}
400     }
401 );
402
403 sub record_distribution_formula_application {
404     my ($self, $conn, $auth, $formulae, $li_id) = @_;
405
406     my $e = new_editor("authtoken" => $auth, "xact" => 1);
407     return $e->die_event unless $e->checkauth;
408
409     # We need this to determine relevant OU for testing permissions...
410     my $li = $e->retrieve_acq_lineitem([
411         $li_id, {
412             "flesh" => 1,
413             "flesh_fields" => {"jub" => [qw/purchase_order picklist/]}
414         }
415     ]) or return $e->die_event;
416
417     # ... which we do here.
418     my $ou;
419     if ($li->purchase_order) {
420         $ou = $li->purchase_order->ordering_agency;
421     } elsif ($li->picklist) {
422         $ou = $li->picklist->org_unit;
423     } else {
424         $e->rollback;
425         return new OpenILS::Event("BAD_PARAMS");
426     }
427
428     return $e->die_event unless $e->allowed("CREATE_PURCHASE_ORDER", $ou);
429
430     # Just deal with it if $formulate is a scalar instead of an array.
431     $formulae = [ $formulae ] if not ref $formulae;
432
433     my @results = ();
434     foreach (@{$formulae}) {
435         my $acqdfa = new Fieldmapper::acq::distribution_formula_application;
436
437         $acqdfa->creator($e->requestor->id);
438         $acqdfa->formula($_);
439         $acqdfa->lineitem($li_id);
440
441         $acqdfa = $e->create_acq_distribution_formula_application($acqdfa)
442             or return $e->die_event;
443         push @results, $acqdfa->id;
444     }
445
446     $e->commit or return $e->die_event;
447     \@results;
448 }
449
450
451 __PACKAGE__->register_method(
452         method => 'ranged_distrib_formulas',
453         api_name        => 'open-ils.acq.distribution_formula.ranged.retrieve',
454     stream => 1,
455         signature => {
456         desc => 'Ranged distribution formulas, fleshed with entries',
457         params => [
458             {desc => 'Authentication token', type => 'string'},
459             {desc => "offset", type => "number"},
460             {desc => "limit", type => "number"}
461         ],
462         return => {desc => 'List of distribution formulas'}
463     }
464 );
465
466 sub ranged_distrib_formulas {
467     my ($self, $conn, $auth, $offset, $limit) = @_;
468
469     $offset ||= 0;
470     $limit ||= 10;
471
472     my $e = new_editor(authtoken=>$auth);
473     return $e->event unless $e->checkauth;
474     my $orgs = $U->user_has_work_perm_at($e, 'CREATE_PICKLIST', {descendants =>1});
475
476     my $forms = $e->search_acq_distribution_formula([
477         {owner => $orgs},
478         {
479             flesh => 1, 
480             flesh_fields => {acqdf => ['entries']},
481             order_by => {acqdf => "name"},
482             limit => $limit,
483             offset => $offset
484         }
485     ]) or return $e->die_event;
486
487     for (@$forms) {
488
489         # how many times has this DF been used
490         my $count = $e->json_query({
491             select => {acqdfa => [{column => 'formula', aggregate => 1, transform => 'count', alias => 'count'}]}, 
492             from => 'acqdfa', 
493             where => {formula => $_->id}
494         })->[0];
495
496         $_->use_count($count->{count});
497         $conn->respond($_);
498     }
499
500     return undef;
501 }
502
503 __PACKAGE__->register_method(
504         method => "ranged_distrib_formula_applications",
505         api_name => "open-ils.acq.distribution_formula_application.ranged.retrieve",
506     stream => 1,
507         signature => {
508         desc => "Ranged distribution formulas applications, fleshed with formulas and users",
509         params => [
510             {desc => "Authentication token", type => "string"},
511             {desc => "Lineitem Id", type => "number"}
512         ],
513         return => {desc => "List of distribution formula applications"}
514     }
515 );
516
517 sub ranged_distrib_formula_applications {
518     my ($self, $conn, $auth, $li_id) = @_;
519
520     my $e = new_editor("authtoken" => $auth);
521     return $e->event unless $e->checkauth;
522
523     my $li = $e->retrieve_acq_lineitem([
524         $li_id, {
525             "flesh" => 1,
526             "flesh_fields" => {"jub" => [qw/purchase_order picklist/]}
527         }
528     ]) or return $e->die_event;
529
530     if ($li->picklist) {
531         return $e->die_event unless $e->allowed(
532             "VIEW_PICKLIST", $li->picklist->org_unit
533         );
534     } elsif ($li->purchase_order) {
535         return $e->die_event unless $e->allowed(
536             "VIEW_PURCHASE_ORDER", $li->purchase_order->ordering_agency
537         );
538     } else {
539         # For the moment no use cases are forseen for using this
540         # method with LIs that don't belong to a PL or a PO.
541         $e->disconnect;
542         return new OpenILS::Event("BAD_PARAMS", "note" => "Freestanding LI");
543     }
544
545     my $dfa = $e->search_acq_distribution_formula_application([
546         {"lineitem" => $li_id},
547         {"flesh" => 1, "flesh_fields" => {"acqdfa" => [qw/formula creator/]}}
548     ]);
549
550     $conn->respond($_) foreach (@$dfa);
551
552     $e->disconnect;
553     undef;
554 }
555
556 1;