1 package OpenILS::Application::Acq::Picklist;
2 use base qw/OpenILS::Application/;
3 use strict; use warnings;
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;
12 use OpenILS::Application::AppUtils;
13 use OpenSRF::Utils::Cache;
18 use Digest::MD5 qw/md5_hex/;
20 my $U = 'OpenILS::Application::AppUtils';
23 __PACKAGE__->register_method(
24 method => 'create_picklist',
25 api_name => 'open-ils.acq.picklist.create',
27 desc => 'Creates a new picklist',
29 {desc => 'Authentication token', type => 'string'},
30 {desc => 'Picklist object to create', type => 'object'}
32 return => {desc => 'The ID of the new picklist'}
37 my($self, $conn, $auth, $picklist) = @_;
38 my $e = new_editor(xact=>1, authtoken=>$auth);
39 return $e->die_event unless $e->checkauth;
40 $picklist->creator($e->requestor->id);
41 $picklist->editor($e->requestor->id);
42 $picklist->org_unit($e->requestor->ws_ou) unless $picklist->org_unit;
43 return $e->die_event unless $e->allowed('CREATE_PICKLIST', $picklist->org_unit);
44 return OpenILS::Event->new('BAD_PARAMS')
45 unless $e->requestor->id == $picklist->owner;
46 $e->create_acq_picklist($picklist) or return $e->die_event;
52 __PACKAGE__->register_method(
53 method => 'update_picklist',
54 api_name => 'open-ils.acq.picklist.update',
56 desc => 'Updates a new picklist',
58 {desc => 'Authentication token', type => 'string'},
59 {desc => 'Picklist object to update', type => 'object'}
61 return => {desc => '1 on success, Event on error'}
66 my($self, $conn, $auth, $picklist) = @_;
67 my $e = new_editor(xact=>1, authtoken=>$auth);
68 return $e->die_event unless $e->checkauth;
70 # don't let them change the owner
71 my $o_picklist = $e->retrieve_acq_picklist($picklist->id)
72 or return $e->die_event;
73 if($o_picklist->owner != $e->requestor->id) {
74 return $e->die_event unless
75 $e->allowed('UPDATE_PICKLIST', $o_picklist->org_unit);
77 return OpenILS::Event->new('BAD_PARAMS') unless $o_picklist->org_unit == $picklist->org_unit;
79 $picklist->edit_time('now');
80 $picklist->editor($e->requestor->id);
81 $e->update_acq_picklist($picklist) or return $e->die_event;
86 __PACKAGE__->register_method(
87 method => 'retrieve_picklist',
88 api_name => 'open-ils.acq.picklist.retrieve',
90 desc => 'Retrieves a picklist',
92 {desc => 'Authentication token', type => 'string'},
93 {desc => 'Picklist ID to retrieve', type => 'number'},
94 {desc => 'Options hash, including "flesh_lineitem_count" to get the count of attached entries', type => 'hash'},
96 return => {desc => 'Picklist object on success, Event on error'}
100 sub retrieve_picklist {
101 my($self, $conn, $auth, $picklist_id, $options) = @_;
102 my $e = new_editor(authtoken=>$auth);
103 return $e->event unless $e->checkauth;
105 my $picklist = $e->retrieve_acq_picklist($picklist_id)
108 $picklist->entry_count(retrieve_lineitem_count($e, $picklist_id))
109 if $$options{flesh_lineitem_count};
111 if($e->requestor->id != $picklist->owner) {
112 return $e->event unless
113 $e->allowed('VIEW_PICKLIST', $picklist->org_unit, $picklist);
116 $picklist->owner($e->retrieve_actor_user($picklist->owner))
117 if($$options{flesh_owner});
118 $picklist->owner($e->retrieve_actor_user($picklist->owner)->usrname)
119 if($$options{flesh_username});
125 # Returns the number of entries associated with this picklist
126 sub retrieve_lineitem_count {
127 my($e, $picklist_id) = @_;
128 my $count = $e->json_query({
130 jub => [{transform => 'count', column => 'id', alias => 'count'}]
133 where => {picklist => $picklist_id}}
135 return $count->[0]->{count};
140 __PACKAGE__->register_method(
141 method => 'retrieve_picklist_name',
142 api_name => 'open-ils.acq.picklist.name.retrieve',
144 desc => 'Retrieves a picklist by name. Owner is implied by the caller',
146 {desc => 'Authentication token', type => 'string'},
147 {desc => 'Picklist name to retrieve', type => 'strin'},
149 return => {desc => 'Picklist object on success, null on not found'}
153 sub retrieve_picklist_name {
154 my($self, $conn, $auth, $name) = @_;
155 my $e = new_editor(authtoken=>$auth);
156 return $e->event unless $e->checkauth;
157 my $picklist = $e->search_acq_picklist(
158 {name => $name, owner => $e->requestor->id})->[0];
159 if($e->requestor->id != $picklist->owner) {
160 return $e->event unless
161 $e->allowed('VIEW_PICKLIST', $picklist->org_unit, $picklist);
168 __PACKAGE__->register_method(
169 method => 'retrieve_user_picklist',
170 api_name => 'open-ils.acq.picklist.user.retrieve',
173 desc => 'Retrieves a user\'s picklists',
175 {desc => 'Authentication token', type => 'string'},
176 {desc => 'Options, including "idlist", whch forces the return
177 of a list of IDs instead of objects', type => 'hash'},
179 return => {desc => 'Picklist object on success, Event on error'}
183 sub retrieve_user_picklist {
184 my($self, $conn, $auth, $options) = @_;
185 my $e = new_editor(authtoken=>$auth);
186 return $e->die_event unless $e->checkauth;
188 # don't grab the PL with name == "", because that is the designated temporary picklist
189 my $list = $e->search_acq_picklist([
190 {owner=>$e->requestor->id, name=>{'!='=>''}},
191 {order_by => {acqpl => 'name'}}
196 for my $id (@$list) {
197 if($$options{idlist}) {
200 my $pl = $e->retrieve_acq_picklist($id);
201 $pl->entry_count(retrieve_lineitem_count($e, $id)) if $$options{flesh_lineitem_count};
202 $pl->owner($e->retrieve_actor_user($pl->owner)) if $$options{flesh_owner};
203 $pl->owner($e->retrieve_actor_user($pl->owner)->usrname) if $$options{flesh_username};
212 __PACKAGE__->register_method(
213 method => 'retrieve_all_user_picklist',
214 api_name => 'open-ils.acq.picklist.user.all.retrieve',
217 desc => 'Retrieves all of the picklists a user is allowed to see',
219 {desc => 'Authentication token', type => 'string'},
220 {desc => 'Options, including "idlist", whch forces the return
221 of a list of IDs instead of objects', type => 'hash'},
223 return => {desc => 'Picklist objects on success, Event on error'}
227 sub retrieve_all_user_picklist {
228 my($self, $conn, $auth, $options) = @_;
229 my $e = new_editor(authtoken=>$auth);
230 return $e->event unless $e->checkauth;
232 my $my_list = $e->search_acq_picklist(
233 {owner=>$e->requestor->id, name=>{'!='=>''}}, {idlist=>1});
235 my $picklist_ids = $e->objects_allowed('VIEW_PICKLIST', 'acqpl');
236 my $p_orgs = $U->user_has_work_perm_at($e, 'VIEW_PICKLIST', {descendants =>1});
237 my $picklist_ids_2 = $e->search_acq_picklist(
238 {name=>{'!='=>''}, org_unit => $p_orgs}, {idlist=>1});
240 return undef unless @$my_list or @$picklist_ids or @$picklist_ids_2;
242 my @list = (@$my_list, @$picklist_ids, @$picklist_ids_2);
244 $dedup{$_} = 1 for @list;
247 return \@list if $$options{idlist};
250 my $picklist = $e->retrieve_acq_picklist($pl) or return $e->event;
251 $picklist->entry_count(retrieve_lineitem_count($e, $picklist->id))
252 if($$options{flesh_lineitem_count});
253 $picklist->owner($e->retrieve_actor_user($picklist->owner))
254 if $$options{flesh_owner};
255 $picklist->owner($e->retrieve_actor_user($picklist->owner)->usrname)
256 if $$options{flesh_username};
257 $conn->respond($picklist);
264 __PACKAGE__->register_method(
265 method => 'delete_picklist',
266 api_name => 'open-ils.acq.picklist.delete',
268 desc => q/Deletes a picklist. It also deletes any lineitems in the "new" state.
269 Other attached lineitems are detached'/,
271 {desc => 'Authentication token', type => 'string'},
272 {desc => 'Picklist ID to delete', type => 'number'}
274 return => {desc => '1 on success, Event on error'}
278 sub delete_picklist {
279 my($self, $conn, $auth, $picklist_id) = @_;
280 my $e = new_editor(xact=>1, authtoken=>$auth);
281 return $e->die_event unless $e->checkauth;
283 my $picklist = $e->retrieve_acq_picklist($picklist_id)
284 or return $e->die_event;
285 # don't let anyone delete someone else's picklist
286 if($picklist->owner != $e->requestor->id) {
287 return $e->die_event unless
288 $e->allowed('DELETE_PICKLIST', $picklist->org_unit, $picklist);
291 # delete all 'new' lineitems
292 my $lis = $e->search_acq_lineitem({picklist => $picklist->id, state => 'new'});
294 $e->delete_acq_lineitem($li) or return $e->die_event;
297 # detach all non-'new' lineitems
298 $lis = $e->search_acq_lineitem({picklist => $picklist->id, state => {'!=' => 'new'}});
301 $e->update_acq_lineitem($li) or return $e->die_event;
304 # remove any picklist-specific object perms
305 my $ops = $e->search_permission_usr_object_perm_map({object_type => 'acqpl', object_id => "".$picklist->id});
307 $e->delete_usr_object_perm_map($op) or return $e->die_event;
311 $e->delete_acq_picklist($picklist) or return $e->die_event;
316 __PACKAGE__->register_method(
317 method => 'retrieve_pl_lineitem',
318 api_name => 'open-ils.acq.lineitem.picklist.retrieve',
321 desc => 'Retrieves lineitem objects according to picklist',
323 {desc => 'Authentication token', type => 'string'},
324 {desc => 'Picklist ID whose entries to retrieve', type => 'number'},
325 {desc => q/Options, including
326 "sort_attr", which defines the attribute to sort on;
327 "sort_attr_type", which defines the attribute type sort on;
328 "sort_dir", which defines the sort order between "asc" and "desc";
329 "limit", retrieval limit;
330 "offset", retrieval offset;
331 "idlist", return a list of IDs instead of objects
332 "flesh_attrs", additionaly return the list of flattened attributes
333 "clear_marc", discards the raw MARC data to reduce data size
337 return => {desc => 'Array of lineitem objects or IDs, on success, Event on error'}
342 my $PL_ENTRY_JSON_QUERY = {
343 select => {jub => ["id"], "acqlia" => ["attr_value"]},
348 "field" => "lineitem",
351 "attr_type" => "lineitem_marc_attr_definition",
352 "attr_name" => "author"
357 "order_by" => {"acqlia" => {"attr_value" => {"direction"=>"asc"}}},
359 "where" => {"+jub" => {"picklist"=>2}},
363 sub retrieve_pl_lineitem {
364 my($self, $conn, $auth, $picklist_id, $options) = @_;
365 my $e = new_editor(authtoken=>$auth);
366 return $e->event unless $e->checkauth;
368 # collect the retrieval options
369 my $sort_attr = $$options{sort_attr} || 'title';
370 my $sort_attr_type = $$options{sort_attr_type} || 'lineitem_marc_attr_definition';
371 my $sort_dir = $$options{sort_dir} || 'asc';
372 my $limit = $$options{limit} || 10;
373 my $offset = $$options{offset} || 0;
375 $PL_ENTRY_JSON_QUERY->{where}->{'+jub'}->{picklist} = $picklist_id;
376 $PL_ENTRY_JSON_QUERY->{from}->{jub}->{acqlia}->{filter}->{attr_name} = $sort_attr;
377 $PL_ENTRY_JSON_QUERY->{from}->{jub}->{acqlia}->{filter}->{attr_type} = $sort_attr_type;
378 $PL_ENTRY_JSON_QUERY->{order_by}->{acqlia}->{attr_value}->{direction} = $sort_dir;
379 $PL_ENTRY_JSON_QUERY->{limit} = $limit;
380 $PL_ENTRY_JSON_QUERY->{offset} = $offset;
382 my $entries = $e->json_query($PL_ENTRY_JSON_QUERY);
385 push(@ids, $_->{id}) for @$entries;
388 if($$options{idlist}) {
394 my $flesh = ($$options{flesh_attrs}) ?
395 {flesh => 1, flesh_fields => {jub => ['attributes']}} : {};
397 $entry = $e->retrieve_acq_lineitem([$id, $flesh]);
398 my $details = $e->search_acq_lineitem_detail({lineitem => $id}, {idlist=>1});
399 $entry->item_count(scalar(@$details));
400 $entry->clear_marc if $$options{clear_marc};
401 $conn->respond($entry);
408 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}}
411 __PACKAGE__->register_method(
413 api_name => 'open-ils.acq.picklist.search.z3950',
416 desc => 'Performs a z3950 federated search and creates a picklist and associated lineitems',
418 {desc => 'Authentication token', type => 'string'},
419 {desc => 'Search definition', type => 'object'},
420 {desc => 'Picklist name, optional', type => 'string'},
426 my($self, $conn, $auth, $search, $name, $options) = @_;
427 my $e = new_editor(authtoken=>$auth);
428 return $e->event unless $e->checkauth;
429 return $e->event unless $e->allowed('CREATE_PICKLIST');
431 $search->{limit} ||= 10;
434 my $ses = OpenSRF::AppSession->create('open-ils.search');
435 my $req = $ses->request('open-ils.search.z3950.search_class', $auth, $search);
439 while(my $resp = $req->recv(timeout=>60)) {
442 $e = new_editor(requestor=>$e->requestor, xact=>1);
443 $picklist = zsearch_build_pl($self, $conn, $auth, $e, $name);
447 my $result = $resp->content;
448 my $count = $result->{count};
449 my $total = (($count < $search->{limit}) ? $count : $search->{limit})+1;
451 $conn->respond({total=>$total, progress=>++$ctr});
453 for my $rec (@{$result->{records}}) {
454 my $li = Fieldmapper::acq::lineitem->new;
455 $li->picklist($picklist->id);
456 $li->source_label($result->{service});
457 $li->selector($e->requestor->id);
458 $li->creator($e->requestor->id);
459 $li->editor($e->requestor->id);
460 $li->edit_time('now');
461 $li->create_time('now');
462 $li->marc($rec->{marcxml});
464 $li->eg_bib_id($rec->{bibid}) if $rec->{bibid};
465 $e->create_acq_lineitem($li) or return $e->die_event;
467 my $response = {total => $total, progress => ++$ctr};
469 if($$options{respond_li}) {
470 $response->{lineitem} = $li;
471 $li->attributes($e->search_acq_lineitem_attr({lineitem => $li->id}))
472 if $$options{flesh_attrs};
473 $li->clear_marc if $$options{clear_marc};
476 $conn->respond($response);
481 return {complete=>1, picklist_id=>$picklist->id};
484 sub zsearch_build_pl {
485 my($self, $conn, $auth, $e, $name) = @_;
488 my $picklist = $e->search_acq_picklist({owner=>$e->requestor->id, name=>$name})->[0];
489 if($name eq '' and $picklist) {
490 my $evt = delete_picklist($self, $conn, $auth, $picklist->id);
491 return $evt unless $evt == 1;
496 $picklist = Fieldmapper::acq::picklist->new;
497 $picklist->owner($e->requestor->id);
498 $picklist->creator($e->requestor->id);
499 $picklist->editor($e->requestor->id);
500 $picklist->edit_time('now');
501 $picklist->name($name);
502 $picklist->org_unit($e->requestor->ws_ou);
503 $e->create_acq_picklist($picklist) or return $e->die_event;
506 $picklist->editor($e->requestor->id);
507 $picklist->edit_time('now');
508 $e->update_acq_picklist($picklist) or return $e->die_event;
516 __PACKAGE__->register_method(
517 method => 'ranged_distrib_formulas',
518 api_name => 'open-ils.acq.distribution_formula.ranged.retrieve',
521 desc => 'Ranged distribution formulas, fleshed with entries',
523 {desc => 'Authentication token', type => 'string'},
525 return => {desc => 'List of distribution formulas'}
529 sub ranged_distrib_formulas {
530 my($self, $conn, $auth) = @_;
531 my $e = new_editor(authtoken=>$auth);
532 return $e->event unless $e->checkauth;
533 my $orgs = $U->user_has_work_perm_at($e, 'CREATE_PICKLIST', {descendants =>1});
534 my $forms = $e->search_acq_distribution_formula([
536 {flesh => 1, flesh_fields => {acqdf => ['entries']}}
538 $conn->respond($_) for @$forms;
542 __PACKAGE__->register_method(
543 method => 'upload_records',
544 api_name => 'open-ils.acq.process_upload_records',
549 my($self, $conn, $auth, $key) = @_;
550 my $e = new_editor(authtoken => $auth, xact => 1);
551 return $e->die_event unless $e->checkauth;
552 my $cache = OpenSRF::Utils::Cache->new;
554 my $data = $cache->get_cache("vandelay_import_spool_$key");
555 my $purpose = $data->{purpose};
556 my $filename = $data->{path};
557 my $provider = $data->{provider};
558 my $picklist = $data->{picklist};
559 my $create_po = $data->{create_po};
560 my $ordering_agency = $data->{ordering_agency};
563 unless(-r $filename) {
564 $logger->error("unable to read MARC file $filename");
566 return OpenILS::Event->new('FILE_UPLOAD_ERROR', payload => {filename => $filename});
569 $provider = $e->retrieve_acq_provider($provider) or return $e->die_event;
572 $picklist = $e->retrieve_acq_picklist($picklist) or return $e->die_event;
573 if($picklist->owner != $e->requestor->id) {
574 return $e->die_event unless
575 $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
580 return $e->die_event unless
581 $e->allowed('CREATE_PURCHASE_ORDER', $ordering_agency);
585 $logger->info("acq processing MARC file=$filename");
587 my $marctype = 'USMARC'; # ?
588 my $batch = new MARC::Batch ($marctype, $filename);
596 $logger->info("processing record $count");
600 } catch Error with { $r = -1; };
605 $logger->warn("Proccessing of record $count in set $key failed. Skipping this record");
612 (my $xml = $r->as_xml_record()) =~ s/\n//sog;
613 $xml =~ s/^<\?xml.+\?\s*>//go;
614 $xml =~ s/>\s+</></go;
615 $xml =~ s/\p{Cc}//go;
616 $xml = $U->entityize($xml);
617 $xml =~ s/[\x00-\x1f]//go;
619 my $li = Fieldmapper::acq::lineitem->new;
620 $li->picklist($picklist->id) if $picklist;
621 $li->purchase_order($purchase_order->id) if $purchase_order;
622 $li->source_label($provider->code); # XXX ??
623 $li->selector($e->requestor->id);
624 $li->creator($e->requestor->id);
625 $li->editor($e->requestor->id);
626 $li->edit_time('now');
627 $li->create_time('now');
629 $li->state('on-order') if $purchase_order;
630 $e->create_acq_lineitem($li) or die $e->die_event;
632 $conn->respond({count => $count}) if (++$count % 5) == 0;
636 $logger->warn("Encountered a bad record at Vandelay ingest: ".$error);
642 $cache->delete_cache('vandelay_import_spool_' . $key);
646 purchase_order => $purchase_order,
647 picklist => $picklist