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;
16 use MARC::File::XML (BinaryEncoding => 'UTF-8');
18 use Digest::MD5 qw/md5_hex/;
19 use OpenILS::Application::Acq::Financials;
22 my $U = 'OpenILS::Application::AppUtils';
25 __PACKAGE__->register_method(
26 method => 'create_picklist',
27 api_name => 'open-ils.acq.picklist.create',
29 desc => 'Creates a new picklist',
31 {desc => 'Authentication token', type => 'string'},
32 {desc => 'Picklist object to create', type => 'object'}
34 return => {desc => 'The ID of the new 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;
54 __PACKAGE__->register_method(
55 method => 'update_picklist',
56 api_name => 'open-ils.acq.picklist.update',
58 desc => 'Updates a new picklist',
60 {desc => 'Authentication token', type => 'string'},
61 {desc => 'Picklist object to update', type => 'object'}
63 return => {desc => '1 on success, Event on error'}
68 my($self, $conn, $auth, $picklist) = @_;
69 my $e = new_editor(xact=>1, authtoken=>$auth);
70 return $e->die_event unless $e->checkauth;
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);
79 return OpenILS::Event->new('BAD_PARAMS') unless $o_picklist->org_unit == $picklist->org_unit;
81 $picklist->edit_time('now');
82 $picklist->editor($e->requestor->id);
83 $e->update_acq_picklist($picklist) or return $e->die_event;
88 __PACKAGE__->register_method(
89 method => 'retrieve_picklist',
90 api_name => 'open-ils.acq.picklist.retrieve',
93 desc => 'Retrieves a picklist',
95 {desc => 'Authentication token', type => 'string'},
96 {desc => 'Picklist ID to retrieve', type => 'number'},
97 {desc => 'Options hash, including "flesh_lineitem_count" to get the count of attached entries', type => 'hash'},
99 return => {desc => 'Picklist object on success, Event on error'}
103 sub retrieve_picklist {
104 my($self, $conn, $auth, $picklist_id, $options) = @_;
105 my $e = new_editor(authtoken=>$auth);
106 return $e->event unless $e->checkauth;
108 return retrieve_picklist_impl($e, $picklist_id, $options);
111 sub retrieve_picklist_impl {
112 my ($e, $picklist_id, $options) = @_;
115 my $picklist = $e->retrieve_acq_picklist($picklist_id)
118 $picklist->entry_count(retrieve_lineitem_count($e, $picklist_id))
119 if $$options{flesh_lineitem_count};
121 if($e->requestor->id != $picklist->owner) {
122 return $e->event unless
123 $e->allowed('VIEW_PICKLIST', $picklist->org_unit, $picklist);
126 $picklist->owner($e->retrieve_actor_user($picklist->owner))
127 if($$options{flesh_owner});
128 $picklist->owner($e->retrieve_actor_user($picklist->owner)->usrname)
129 if($$options{flesh_username});
135 # Returns the number of entries associated with this picklist
136 sub retrieve_lineitem_count {
137 my($e, $picklist_id) = @_;
138 my $count = $e->json_query({
140 jub => [{transform => 'count', column => 'id', alias => 'count'}]
143 where => {picklist => $picklist_id}}
145 return $count->[0]->{count};
150 __PACKAGE__->register_method(
151 method => 'retrieve_picklist_name',
152 api_name => 'open-ils.acq.picklist.name.retrieve',
155 desc => 'Retrieves a picklist by name. Owner is implied by the caller',
157 {desc => 'Authentication token', type => 'string'},
158 {desc => 'Picklist name to retrieve', type => 'string'},
160 return => {desc => 'Picklist object on success, null on not found'}
164 sub retrieve_picklist_name {
165 my($self, $conn, $auth, $name) = @_;
166 my $e = new_editor(authtoken=>$auth);
167 return $e->event unless $e->checkauth;
168 my $picklist = $e->search_acq_picklist(
169 {name => $name, owner => $e->requestor->id})->[0];
170 if($e->requestor->id != $picklist->owner) {
171 return $e->event unless
172 $e->allowed('VIEW_PICKLIST', $picklist->org_unit, $picklist);
179 __PACKAGE__->register_method(
180 method => 'retrieve_user_picklist',
181 api_name => 'open-ils.acq.picklist.user.retrieve',
184 desc => 'Retrieves a user\'s picklists',
186 {desc => 'Authentication token', type => 'string'},
187 {desc => 'Options, including "idlist", whch forces the return
188 of a list of IDs instead of objects', type => 'hash'},
190 return => {desc => 'Picklist object on success, Event on error'}
194 sub retrieve_user_picklist {
195 my($self, $conn, $auth, $options) = @_;
196 my $e = new_editor(authtoken=>$auth);
197 return $e->die_event unless $e->checkauth;
200 # don't grab the PL with name == "", because that is the designated temporary picklist
201 my $list = $e->search_acq_picklist([
203 owner => $e->requestor->id,
206 order_by => $$options{order_by} || {acqpl => 'edit_time DESC'},
207 limit => $$options{limit} || 10,
208 offset => $$options{offset} || 0,
214 for my $id (@$list) {
215 if($$options{idlist}) {
218 my $pl = $e->retrieve_acq_picklist($id);
219 $pl->entry_count(retrieve_lineitem_count($e, $id)) if $$options{flesh_lineitem_count};
220 $pl->owner($e->retrieve_actor_user($pl->owner)) if $$options{flesh_owner};
221 $pl->owner($e->retrieve_actor_user($pl->owner)->usrname) if $$options{flesh_username};
230 __PACKAGE__->register_method(
231 method => 'retrieve_all_user_picklist',
232 api_name => 'open-ils.acq.picklist.user.all.retrieve',
235 desc => 'Retrieves all of the picklists a user is allowed to see',
237 {desc => 'Authentication token', type => 'string'},
238 {desc => 'Options, including "idlist", whch forces the return
239 of a list of IDs instead of objects', type => 'hash'},
241 return => {desc => 'Picklist objects on success, Event on error'}
245 sub retrieve_all_user_picklist {
246 my($self, $conn, $auth, $options) = @_;
247 my $e = new_editor(authtoken=>$auth);
248 return $e->event unless $e->checkauth;
250 my $my_list = $e->search_acq_picklist(
251 {owner=>$e->requestor->id, name=>{'!='=>''}}, {idlist=>1});
253 my $picklist_ids = $e->objects_allowed('VIEW_PICKLIST', 'acqpl');
254 my $p_orgs = $U->user_has_work_perm_at($e, 'VIEW_PICKLIST', {descendants =>1});
255 my $picklist_ids_2 = $e->search_acq_picklist(
256 {name=>{'!='=>''}, org_unit => $p_orgs}, {idlist=>1});
258 return undef unless @$my_list or @$picklist_ids or @$picklist_ids_2;
260 my @list = (@$my_list, @$picklist_ids, @$picklist_ids_2);
262 $dedup{$_} = 1 for @list;
265 return \@list if $$options{idlist};
268 my $picklist = $e->retrieve_acq_picklist($pl) or return $e->event;
269 $picklist->entry_count(retrieve_lineitem_count($e, $picklist->id))
270 if($$options{flesh_lineitem_count});
271 $picklist->owner($e->retrieve_actor_user($picklist->owner))
272 if $$options{flesh_owner};
273 $picklist->owner($e->retrieve_actor_user($picklist->owner)->usrname)
274 if $$options{flesh_username};
275 $conn->respond($picklist);
281 __PACKAGE__->register_method(
282 method => 'retrieve_pl_lineitem',
283 api_name => 'open-ils.acq.lineitem.picklist.retrieve',
286 desc => 'Retrieves lineitem objects according to picklist',
288 {desc => 'Authentication token', type => 'string'},
289 {desc => 'Picklist ID whose entries to retrieve', type => 'number'},
290 {desc => q/Options, including
291 "sort_attr", which defines the attribute to sort on;
292 "sort_attr_type", which defines the attribute type sort on;
293 "sort_dir", which defines the sort order between "asc" and "desc";
294 "limit", retrieval limit;
295 "offset", retrieval offset;
296 "idlist", return a list of IDs instead of objects
297 "flesh_attrs", additionaly return the list of flattened attributes
298 "clear_marc", discards the raw MARC data to reduce data size
299 "flesh_notes", flesh lineitem notes
300 "flesh_cancel_reason", flesh cancel_reason
304 return => {desc => 'Array of lineitem objects or IDs, on success, Event on error'}
309 my $PL_ENTRY_JSON_QUERY = {
310 select => {jub => ["id"], "acqlia" => ["attr_value"]},
315 "field" => "lineitem",
318 "attr_type" => "lineitem_marc_attr_definition",
319 "attr_name" => "author"
324 "order_by" => {"acqlia" => {"attr_value" => {"direction"=>"asc"}}},
326 "where" => {"+jub" => {"picklist"=>2}},
330 sub retrieve_pl_lineitem {
331 my($self, $conn, $auth, $picklist_id, $options) = @_;
332 my $e = new_editor(authtoken=>$auth);
333 return $e->event unless $e->checkauth;
335 # collect the retrieval options
336 my $sort_attr = $$options{sort_attr} || 'title';
337 my $sort_attr_type = $$options{sort_attr_type} || 'lineitem_marc_attr_definition';
338 my $sort_dir = $$options{sort_dir} || 'asc';
339 my $limit = $$options{limit} || 10;
340 my $offset = $$options{offset} || 0;
342 $PL_ENTRY_JSON_QUERY->{where}->{'+jub'}->{picklist} = $picklist_id;
343 $PL_ENTRY_JSON_QUERY->{from}->{jub}->{acqlia}->{filter}->{attr_name} = $sort_attr;
344 $PL_ENTRY_JSON_QUERY->{from}->{jub}->{acqlia}->{filter}->{attr_type} = $sort_attr_type;
345 $PL_ENTRY_JSON_QUERY->{order_by}->{acqlia}->{attr_value}->{direction} = $sort_dir;
346 $PL_ENTRY_JSON_QUERY->{limit} = $limit;
347 $PL_ENTRY_JSON_QUERY->{offset} = $offset;
349 my $entries = $e->json_query($PL_ENTRY_JSON_QUERY);
352 for my $entry (@$entries) {
353 push(@ids, $entry->{id}) unless grep { $_ eq $entry->{id} } @ids;
357 if($$options{idlist}) {
364 if($$options{flesh_attrs} or $$options{flesh_notes} or $$options{flesh_cancel_reason}) {
365 $flesh = {flesh => 2, flesh_fields => {jub => []}};
366 if($$options{flesh_notes}) {
367 push(@{$flesh->{flesh_fields}->{jub}}, 'lineitem_notes');
368 $flesh->{flesh_fields}->{acqlin} = ['alert_text'];
370 push(@{$flesh->{flesh_fields}->{jub}}, 'attributes') if $$options{flesh_attrs};
371 push @{$flesh->{flesh_fields}->{jub}}, 'cancel_reason' if $$options{flesh_cancel_reason};
374 $entry = $e->retrieve_acq_lineitem([$id, $flesh]);
375 my $details = $e->search_acq_lineitem_detail({lineitem => $id}, {idlist=>1});
376 $entry->item_count(scalar(@$details));
377 $entry->clear_marc if $$options{clear_marc};
378 $conn->respond($entry);
386 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}}
392 __PACKAGE__->register_method(
393 method => "record_distribution_formula_application",
394 api_name => "open-ils.acq.distribution_formula.record_application",
396 desc => "Record the application (which actually happens on the " .
397 "client side) of a distribution formula to a PO or a PL",
399 {desc => "Authentication token", type => "string"},
400 {desc => "Formulae applied", "type" => "array"},
401 {desc => "Lineitem ID", "type" => "number"}
403 return => {desc => "acqdfa IDs on success; event on failure"}
407 sub record_distribution_formula_application {
408 my ($self, $conn, $auth, $formulae, $li_id) = @_;
410 my $e = new_editor("authtoken" => $auth, "xact" => 1);
411 return $e->die_event unless $e->checkauth;
413 # We need this to determine relevant OU for testing permissions...
414 my $li = $e->retrieve_acq_lineitem([
417 "flesh_fields" => {"jub" => [qw/purchase_order picklist/]}
419 ]) or return $e->die_event;
421 # ... which we do here.
423 if ($li->purchase_order) {
424 $ou = $li->purchase_order->ordering_agency;
425 } elsif ($li->picklist) {
426 $ou = $li->picklist->org_unit;
429 return new OpenILS::Event("BAD_PARAMS");
432 return $e->die_event unless $e->allowed("CREATE_PURCHASE_ORDER", $ou);
434 # Just deal with it if $formulate is a scalar instead of an array.
435 $formulae = [ $formulae ] if not ref $formulae;
438 foreach (@{$formulae}) {
439 my $acqdfa = new Fieldmapper::acq::distribution_formula_application;
441 $acqdfa->creator($e->requestor->id);
442 $acqdfa->formula($_);
443 $acqdfa->lineitem($li_id);
445 $acqdfa = $e->create_acq_distribution_formula_application($acqdfa)
446 or return $e->die_event;
447 push @results, $acqdfa->id;
450 $e->commit or return $e->die_event;
455 __PACKAGE__->register_method(
456 method => 'ranged_distrib_formulas',
457 api_name => 'open-ils.acq.distribution_formula.ranged.retrieve',
460 desc => 'Ranged distribution formulas, fleshed with entries',
462 {desc => 'Authentication token', type => 'string'},
463 {desc => "offset", type => "number"},
464 {desc => "limit", type => "number"}
466 return => {desc => 'List of distribution formulas'}
470 sub ranged_distrib_formulas {
471 my ($self, $conn, $auth, $offset, $limit) = @_;
476 my $e = new_editor(authtoken=>$auth);
477 return $e->event unless $e->checkauth;
478 my $orgs = $U->user_has_work_perm_at($e, 'CREATE_PICKLIST', {descendants =>1});
480 my $forms = $e->search_acq_distribution_formula([
484 flesh_fields => {acqdf => ['entries']},
485 order_by => {acqdf => "name"},
489 ]) or return $e->die_event;
493 # how many times has this DF been used
494 my $count = $e->json_query({
495 select => {acqdfa => [{column => 'formula', aggregate => 1, transform => 'count', alias => 'count'}]},
497 where => {formula => $_->id}
500 $_->use_count($count->{count});
507 __PACKAGE__->register_method(
508 method => "ranged_distrib_formula_applications",
509 api_name => "open-ils.acq.distribution_formula_application.ranged.retrieve",
512 desc => "Ranged distribution formulas applications, fleshed with formulas and users",
514 {desc => "Authentication token", type => "string"},
515 {desc => "Lineitem Id", type => "number"}
517 return => {desc => "List of distribution formula applications"}
521 sub ranged_distrib_formula_applications {
522 my ($self, $conn, $auth, $li_id) = @_;
524 my $e = new_editor("authtoken" => $auth);
525 return $e->event unless $e->checkauth;
527 my $li = $e->retrieve_acq_lineitem([
530 "flesh_fields" => {"jub" => [qw/purchase_order picklist/]}
532 ]) or return $e->die_event;
535 return $e->die_event unless $e->allowed(
536 "VIEW_PICKLIST", $li->picklist->org_unit
538 } elsif ($li->purchase_order) {
539 return $e->die_event unless $e->allowed(
540 "VIEW_PURCHASE_ORDER", $li->purchase_order->ordering_agency
543 # For the moment no use cases are forseen for using this
544 # method with LIs that don't belong to a PL or a PO.
546 return new OpenILS::Event("BAD_PARAMS", "note" => "Freestanding LI");
549 my $dfa = $e->search_acq_distribution_formula_application([
550 {"lineitem" => $li_id},
551 {"flesh" => 1, "flesh_fields" => {"acqdfa" => [qw/formula creator/]}}
554 $conn->respond($_) foreach (@$dfa);