]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Lineitem.pm
LP1929741 ACQ Selection List & PO Angluar Port
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Acq / Lineitem.pm
1 package OpenILS::Application::Acq::Lineitem;
2 use base qw/OpenILS::Application/;
3 use strict; use warnings;
4
5 use OpenILS::Event;
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::Application::AppUtils;
12 use OpenILS::Application::Acq::Financials;
13 use OpenILS::Application::Cat::BibCommon;
14 use OpenILS::Application::Cat::AssetCommon;
15 use OpenILS::Application::Acq::Lineitem::BatchUpdate;
16 use OpenILS::Application::Acq::Common;
17 my $U = 'OpenILS::Application::AppUtils';
18 my $AC = 'OpenILS::Application::Acq::Common';
19
20
21 __PACKAGE__->register_method(
22     method    => 'create_lineitem',
23     api_name  => 'open-ils.acq.lineitem.create',
24     signature => {
25         desc   => 'Creates a lineitem',
26         params => [
27             {desc => 'Authentication token', type => 'string'},
28             {desc => 'The lineitem object to create', type => 'object'},
29         ],
30         return => {desc => 'ID of newly created lineitem on success, Event on error'}
31     }
32 );
33
34 sub create_lineitem {
35     my($self, $conn, $auth, $li) = @_;
36     my $e = new_editor(xact=>1, authtoken=>$auth);
37     return $e->die_event unless $e->checkauth;
38
39
40     if($li->picklist) {
41         my $picklist = $e->retrieve_acq_picklist($li->picklist)
42             or return $e->die_event;
43
44         if($picklist->owner != $e->requestor->id) {
45             return $e->die_event unless 
46                 $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
47         }
48     
49         # indicate the picklist was updated
50         $picklist->edit_time('now');
51         $picklist->editor($e->requestor->id);
52         $e->update_acq_picklist($picklist) or return $e->die_event;
53     }
54
55     my $po;
56     if($li->purchase_order) {
57         $po = $e->retrieve_acq_purchase_order([
58             $li->purchase_order,
59             {flesh => 1, flesh_fields => {acqpo => ['provider']}}
60             ]) or return $e->die_event;
61         return $e->die_event unless 
62             $e->allowed('MANAGE_PROVIDER', $po->ordering_agency, $po);
63
64         $li->provider($po->provider) unless defined $li->provider;
65     }
66
67     $li->selector($e->requestor->id);
68     $li = $e->create_acq_lineitem($li) or return $e->die_event;
69
70     # if no price is set, see if it can be extracted from the price attribute.
71     if (not defined $li->estimated_unit_price) { # '0' is valid
72         my $evt = set_li_price_from_attr($e, $li);
73         return $evt if $evt;
74     }
75
76     if ($po) {
77         # apply the default number of copies for this provider
78         for (1 .. $po->provider->default_copy_count) {
79             my $lid = Fieldmapper::acq::lineitem_detail->new;
80             $lid->lineitem($li->id);
81             $lid->owning_lib($e->requestor->ws_ou);
82             $e->create_acq_lineitem_detail($lid) or return $e->die_event;
83         }
84     }
85
86     $e->commit;
87     return $li->id;
88 }
89
90 # See if we have a value for the price attribute.  If so, apply the price 
91 # value to the lineitem.
92 # Returns undef on success, Event on error.
93 sub set_li_price_from_attr {
94     my ($e, $li) = @_;
95
96     my $attr = $e->search_acq_lineitem_attr({
97         lineitem => $li->id,
98         attr_type => 'lineitem_marc_attr_definition',
99         attr_name  => 'price'
100     })->[0];
101
102     return unless $attr;
103
104     my $val = $attr->attr_value;
105
106     return unless defined $val; # '0' is valid
107
108     $li->estimated_unit_price($val);
109
110     if (!$e->update_acq_lineitem($li)) {
111         $e->rollback;
112         return OpenILS::Event->new('BAD_PARAMS', 
113             desc => "Invalid lineitem price value: '$val'") ;
114     }
115
116     return undef;
117 }
118
119
120 __PACKAGE__->register_method(
121     method    => 'retrieve_lineitem',
122     api_name  => 'open-ils.acq.lineitem.retrieve',
123     authoritative => 1,
124     signature => {
125         desc   => 'Retrieves a lineitem',
126         params => [
127             {desc => 'Authentication token',    type => 'string'},
128             {desc => 'lineitem ID to retrieve', type => 'number'},
129             {options => q/Hash of options, including:
130 flesh_attrs         : for attributes,
131 flesh_notes         : for notes,
132 flesh_cancel_reason : for cancel reason,
133 flesh_li_details    : for order details objects,
134 flesh_provider      : for provider,
135 flesh_claim_policy  : for claim policy,
136 flesh_queued_record : for queued bib record from Vandelay,
137 clear_marc          : to clear marcxml from lineitem/, type => 'hash'},
138         ],
139         return => {desc => 'lineitem object on success, Event on error'}
140     }
141 );
142
143
144 sub retrieve_lineitem {
145     my($self, $conn, $auth, $li_id, $options) = @_;
146     my $e = new_editor(authtoken=>$auth);
147     return $e->die_event unless $e->checkauth;
148     return retrieve_lineitem_impl($e, $li_id, $options);
149 }
150
151 sub retrieve_lineitem_impl {
152     my ($e, $li_id, $options, $no_auth) = @_;   # no_auth needed for EDI scripts
153     $options ||= {};
154
155     my $flesh = {
156         flesh => 3,
157         flesh_fields => {
158             jub => ['purchase_order', 'picklist'], # needed for permission check
159             acqlid => [],
160             acqlin => []
161         }
162     };
163
164     my $fields = $flesh->{flesh_fields};
165
166     push(@{$fields->{jub}   },    'attributes') if $$options{flesh_attrs};
167     push(@{$fields->{jub}   },'lineitem_notes') if $$options{flesh_notes};
168     push(@{$fields->{acqlin}},    'alert_text') if $$options{flesh_notes};
169     push(@{$fields->{jub}   }, 'order_summary') if $$options{flesh_order_summary};
170     push(@{$fields->{jub}   }, 'cancel_reason') if $$options{flesh_cancel_reason};
171     push(@{$fields->{jub}   },      'provider') if $$options{flesh_provider};
172     push(@{$fields->{jub}   },  'claim_policy') if $$options{flesh_claim_policy};
173     push(@{$fields->{jub}   }, 'queued_record') if $$options{flesh_queued_record};
174     push(@{$fields->{jub}   },       'creator') if $$options{flesh_creator};
175     push(@{$fields->{jub}   },        'editor') if $$options{flesh_editor};
176     push(@{$fields->{jub}   },      'selector') if $$options{flesh_selector};
177
178     if ($$options{flesh_formulas}) {
179         push(@{$fields->{jub}},    'distribution_formulas');
180         push(@{$fields->{acqdfa}}, 'formula');
181         push(@{$fields->{acqdfa}}, 'creator');
182     }
183
184     if($$options{flesh_li_details}) {
185         push(@{$fields->{jub}   }, 'lineitem_details');
186         push(@{$fields->{acqlid}}, 'fund'         ) if $$options{flesh_fund};
187         push(@{$fields->{acqlid}}, 'fund_debit'   ) if $$options{flesh_fund_debit};
188         push(@{$fields->{acqlid}}, 'cancel_reason') if $$options{flesh_cancel_reason};
189         push(@{$fields->{acqlid}}, 'circ_modifier') if $$options{flesh_circ_modifier};
190         push(@{$fields->{acqlid}}, 'location')      if $$options{flesh_location};
191         push(@{$fields->{acqlid}}, 'eg_copy_id')    if $$options{flesh_copies};
192     }
193
194     if($$options{clear_marc}) { # avoid fetching marc blob
195         my @fields = grep { $_ ne 'marc' } Fieldmapper::acq::lineitem->new->real_fields;
196         $flesh->{select} = {jub => [@fields]};
197     }
198
199     my $li = $e->retrieve_acq_lineitem([$li_id, $flesh]) or return $e->event;
200
201     # collect the # of lids
202     if($$options{flesh_li_details}) {
203         $li->item_count(scalar(@{$li->lineitem_details}));
204     } else {
205         my $details = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
206         $li->item_count(scalar(@$details));
207     }
208
209     # attach claims to LIDs
210     if($$options{flesh_li_details}) {
211         foreach (@{$li->lineitem_details}) {
212             $_->claims(
213                 $e->search_acq_claim([
214                     {"lineitem_detail", $_->id}, {
215                         "flesh" => 1, "flesh_fields" => {"acqcl" => ["type"]}
216                     }
217                 ])
218             );
219         }
220     }
221
222     return $e->event unless ((
223         $li->purchase_order and 
224             ($no_auth or $e->allowed(['VIEW_PURCHASE_ORDER', 'CREATE_PURCHASE_ORDER'], 
225                 $li->purchase_order->ordering_agency, $li->purchase_order))
226         ) or (
227         $li->picklist and !$li->purchase_order and # user doesn't have view_po perms
228             ($no_auth or $e->allowed(['VIEW_PICKLIST', 'CREATE_PICKLIST'], 
229                 $li->picklist->org_unit, $li->picklist))
230     ));
231
232     unless ($$options{flesh_po}) {
233         $li->purchase_order(
234             $li->purchase_order ? $li->purchase_order->id : undef
235         );
236     }
237     unless ($$options{flesh_pl}) {
238         $li->picklist($li->picklist ? $li->picklist->id : undef);
239     }
240     return $li;
241 }
242
243 __PACKAGE__->register_method(
244     method    => 'retrieve_lineitem_batch',
245     api_name  => 'open-ils.acq.lineitem.retrieve.batch',
246     stream => 1,
247     max_bundle_count => 1,
248     signature => {
249         desc   => q/
250             Retrieves a set of lineitems.  
251             See open-ils.acq.lineitem.retrieve/,
252         params => [
253             {desc => 'Authentication token',    type => 'string'},
254             {desc => 'Array of lineitem IDs to retrieve', type => 'array'},
255             {options => q/See open-ils.acq.lineitem.retrieve/}
256         ],
257         return => {desc => 'Stream of lineitems, Event on error'}
258     }
259 );
260
261
262 sub retrieve_lineitem_batch {
263     my($self, $client, $auth, $li_ids, $options) = @_;
264     my $e = new_editor(authtoken => $auth, xact => 1);
265     return $e->die_event unless $e->checkauth;
266
267     for my $li_id (@$li_ids) {
268         $client->respond({
269             id => $li_id,
270             lineitem => retrieve_lineitem_impl($e, $li_id, $options),
271             existing_copies => $AC->li_existing_copies($e, $li_id)
272         });
273     }
274
275     $e->rollback;
276
277     return undef;
278 }
279
280
281
282 __PACKAGE__->register_method(
283     method    => 'delete_lineitem',
284     api_name  => 'open-ils.acq.lineitem.delete',
285     signature => {
286         desc   => 'Deletes a lineitem',
287         params => [
288             {desc => 'Authentication token',  type => 'string'},
289             {desc => 'lineitem ID to delete', type => 'number'},
290         ],
291         return => {desc => '1 on success, Event on error'}
292     }
293 );
294
295 __PACKAGE__->register_method(
296     method    => 'delete_lineitem',
297     api_name  => 'open-ils.acq.purchase_order.lineitem.delete',
298     signature => {
299         desc   => 'Deletes a lineitem from a purchase order',
300         params => [
301             {desc => 'Authentication token',  type => 'string'},
302             {desc => 'lineitem ID to delete', type => 'number'},
303         ],
304         return => {desc => '1 on success, Event on error'}
305     }
306 );
307
308 __PACKAGE__->register_method(
309     method    => 'delete_lineitem',
310     api_name  => 'open-ils.acq.picklist.lineitem.delete',
311     signature => {
312         desc   => 'Deletes a lineitem from a picklist',
313         params => [
314             {desc => 'Authentication token',  type => 'string'},
315             {desc => 'lineitem ID to delete', type => 'number'},
316         ],
317         return => {desc => '1 on success, Event on error'}
318     }
319 );
320
321 sub delete_lineitem {
322     my($self, $conn, $auth, $li_id) = @_;
323     my $e = new_editor(xact=>1, authtoken=>$auth);
324     return $e->die_event unless $e->checkauth;
325
326     my $li = $e->retrieve_acq_lineitem($li_id)
327         or return $e->die_event;
328
329     # XXX check state
330
331     if($li->picklist) {
332         my $picklist = $e->retrieve_acq_picklist($li->picklist)
333             or return $e->die_event;
334         return OpenILS::Event->new('BAD_PARAMS') 
335             if $picklist->owner != $e->requestor->id;
336     } else {
337         # check PO perms
338     }
339
340     # once a LI is attached to a PO, deleting it
341     # from a picklist means *detaching* it from the picklist
342     if ($self->api_name =~ /picklist/ && $li->purchase_order) {
343         $li->clear_picklist;
344         my $evt = update_lineitem_impl($e, $li);
345         return $evt if $evt;
346         $e->commit;
347         return 1;
348     }
349
350     # delete the attached lineitem_details
351     my $lid_ids = $e->search_acq_lineitem_detail(
352         {lineitem => $li_id}, {idlist=>1});
353
354     for my $lid_id (@$lid_ids) {
355         $e->delete_acq_lineitem_detail(
356             $e->retrieve_acq_lineitem_detail($lid_id))
357             or return $e->die_event;
358     }
359
360     $e->delete_acq_lineitem($li) or return $e->die_event;
361     $e->commit;
362     return 1;
363 }
364
365
366 __PACKAGE__->register_method(
367     method    => 'update_lineitem',
368     api_name  => 'open-ils.acq.lineitem.update',
369     signature => {
370         desc   => 'Update one or many lineitems',
371         params => [
372             {desc => 'Authentication token',   type => 'string'},
373             {desc => 'lineitem object update', type => 'object'}
374         ],
375         return => {desc => '1 on success, Event on error'}
376     }
377 );
378
379 sub update_lineitem {
380     my($self, $conn, $auth, $li) = @_;
381     my $e = new_editor(xact=>1, authtoken=>$auth);
382     return $e->die_event unless $e->checkauth;
383
384     $li = [$li] unless ref $li eq "ARRAY";
385     foreach (@$li) {
386         my $evt = update_lineitem_impl($e, $_);
387         return $evt if $evt;
388     }
389
390     $e->commit;
391     return 1;
392 }
393
394 sub update_lineitem_impl {
395     my($e, $li) = @_;
396
397     my $orig_li = $e->retrieve_acq_lineitem([
398         $li->id,
399         {   flesh => 1, # grab the lineitem with picklist attached
400             flesh_fields => {jub => ['picklist', 'purchase_order']}
401         }
402     ]) or return $e->die_event;
403
404     # the marc may have been cleared on retrieval...
405     $li->marc($orig_li->marc) unless $li->marc;
406
407     $li->editor($e->requestor->id);
408     $li->edit_time('now');
409     $e->update_acq_lineitem($li) or return $e->die_event;
410     return undef;
411 }
412
413 __PACKAGE__->register_method(
414     method => 'lineitem_search',
415     api_name => 'open-ils.acq.lineitem.search',
416     stream => 1,
417     signature => {
418         desc => 'Searches lineitems',
419         params => [
420             {desc => 'Authentication token',       type => 'string'},
421             {desc => 'Search definition',          type => 'object'},
422             {desc => 'Options hash.  idlist=true', type => 'object'},
423             {desc => 'List of lineitems',          type => 'object/number'},
424         ]
425     }
426 );
427
428 sub lineitem_search {
429     my($self, $conn, $auth, $search, $options) = @_;
430     my $e = new_editor(authtoken=>$auth);
431     return $e->event unless $e->checkauth;
432     return $e->event unless $e->allowed('CREATE_PICKLIST');
433     # XXX needs permissions consideration
434     my $lis = $e->search_acq_lineitem($search, {idlist=>1});
435     for my $li_id (@$lis) {
436         if($$options{idlist}) {
437             $conn->respond($li_id);
438         } else {
439             my $res = retrieve_lineitem($self, $conn, $auth, $li_id, $options);
440             $conn->respond($res) unless $U->event_code($res);
441         }
442     }
443     return undef;
444 }
445
446 __PACKAGE__->register_method (
447     method    => 'lineitems_related_by_bib',
448     api_name  => 'open-ils.acq.lineitems_for_bib.by_bib_id',
449     stream    => 1,
450     signature => q/
451         Retrieves lineitems attached to same bib record, subject to the PO ordering agency.  This variant takes the bib id.
452         @param authtoken Login session key
453         @param bib_id Id for the pertinent bib record.
454         @param options Object for tweaking the selection criteria and fleshing options.
455     /
456 );
457
458 __PACKAGE__->register_method (
459     method    => 'lineitems_related_by_bib',
460     api_name  => 'open-ils.acq.lineitems_for_bib.by_lineitem_id',
461     stream    => 1,
462     signature => q/
463         Retrieves lineitems attached to same bib record, subject to the PO ordering agency.  This variant takes the id for any of the pertinent lineitems.
464         @param authtoken Login session key
465         @param bib_id Id for a pertinent lineitem.
466         @param options Object for tweaking the selection criteria and fleshing options.
467     /
468 );
469
470 __PACKAGE__->register_method (
471     method    => 'lineitems_related_by_bib',
472     api_name  => 'open-ils.acq.lineitems_for_bib.by_lineitem_id.count',
473     stream    => 1,
474     signature => q/See open-ils.acq.lineitems_for_bib.by_lineitem_id. This version returns numbers of lineitems only (XXX may count lineitems we don't actually have permission to retrieve)/
475 );
476
477 sub lineitems_related_by_bib {
478     my($self, $conn, $auth, $test_value, $options) = @_;
479     my $e = new_editor(authtoken => $auth);
480     return $e->event unless $e->checkauth;
481
482     my $perm_orgs = $U->user_has_work_perm_at($e, 'VIEW_PURCHASE_ORDER', {descendants =>1}, $e->requestor->id);
483
484     my $query = {
485         "select"=>{"jub"=>["id"]},
486         "from"=>{"jub" => {"acqpo" => {type => 'left'}, "acqpl" => {type => 'left'}}}, 
487         "where"=>{
488             '-or' => [
489                 { "+acqpo"=>{ "ordering_agency" => $perm_orgs } },
490                 { '+acqpl' => { org_unit => $perm_orgs } }
491             ]
492         },
493         "order_by"=>[{"class"=>"jub", "field"=>"create_time", "direction"=>"desc"}]
494     };
495
496     # Be sure we just return the original LI if no related bibs
497     if ($self->api_name =~ /by_lineitem_id/) {
498         my $orig = retrieve_lineitem($self, $conn, $auth, $test_value) or
499             return $e->die_event;
500         if ($test_value = $orig->eg_bib_id) {
501             $query->{"where"}->{"eg_bib_id"} = $test_value;
502         } else {
503             $query->{"where"}->{"id"} = $orig->id;
504         }
505     } elsif ($test_value) {
506         $query->{"where"}->{"eg_bib_id"} = $test_value;
507     } else {
508         $e->disconnect;
509         return new OpenILS::Event("BAD_PARAMS", "Null bib id");
510     }
511
512     if ($options && defined $options->{lineitem_state}) {
513         $query->{'where'}{'jub'}{'state'} = $options->{lineitem_state};
514     }
515
516     if ($options && defined $options->{po_state}) {
517         $query->{'where'}{'+acqpo'}{'state'} = $options->{po_state};
518     }
519
520     if ($options && defined $options->{order_by}) {
521         $query->{'order_by'} = $options->{order_by};
522     }
523
524     my $results = $e->json_query($query);
525     if ($self->api_name =~ /count$/) {
526         return scalar(@$results);
527     } else {
528         for my $result (@$results) {
529             # retrieve_lineitem takes care of POs and PLs and also handles
530             # options like flesh_notes and permissions checking.
531             $conn->respond(
532                 retrieve_lineitem($self, $conn, $auth, $result->{"id"}, $options)
533             );
534         }
535     }
536
537     return undef;
538 }
539
540
541 __PACKAGE__->register_method(
542     method    => "lineitem_search_by_attributes",
543     api_name  => "open-ils.acq.lineitem.search.by_attributes",
544     stream    => 1,
545     signature => {
546         desc   => "Performs a search against lineitem_attrs",
547         params => [
548             {desc => "Authentication token", type => "string"},
549             {   desc => q/
550 Search definition:
551     attr_value_pairs : list of pairs of (attr definition ID, attr value) where value can be scalar (fuzzy match) or array (exact match)
552     li_states        : list of lineitem states
553     po_agencies      : list of purchase order ordering agencies (org) ids
554
555 At least one of these search terms is required.
556                 /,
557                 type => "object"},
558             {   desc => q/
559 Options hash:
560     idlist      : if set, only return lineitem IDs
561     clear_marc  : if set, strip the MARC xml from the lineitem before delivery
562     flesh_attrs : flesh lineitem attributes;
563                 /,
564                 type => "object"}
565         ]
566     }
567 );
568
569 __PACKAGE__->register_method(
570     method    => "lineitem_search_by_attributes",
571     api_name  => "open-ils.acq.lineitem.search.by_attributes.ident",
572     stream    => 1,
573     signature => {
574         desc => "Performs a search against lineitem_attrs where ident is true.  ".
575             "See open-ils.acq.lineitem.search.by_attributes for params."
576     }
577 );
578
579 sub lineitem_search_by_attributes {
580     my ($self, $conn, $auth, $search, $options) = @_;
581
582     my $e = new_editor(authtoken => $auth, xact => 1);
583     return $e->die_event unless $e->checkauth;
584     # XXX needs permissions consideration
585
586     return [] unless $search;
587     my $attr_value_pairs = $search->{attr_value_pairs};
588     my $li_states = $search->{li_states};
589     my $po_agencies = $search->{po_agencies}; # XXX if none, base it on perms
590
591     my $query = {
592         "select" => {"acqlia" =>
593             [{"column" => "lineitem", "transform" => "distinct"}]
594         },
595         "from" => {
596             "acqlia" => {
597                 "acqliad" => {"field" => "id", "fkey" => "definition"},
598                 "jub" => {
599                     "field" => "id",
600                     "fkey" => "lineitem",
601                     "join" => {
602                         "acqpo" => {
603                             "type" => "left",
604                             "field" => "id",
605                             "fkey" => "purchase_order"
606                         }
607                     }
608                 }
609             }
610         }
611     };
612
613     my $where = {};
614     $where->{"+acqliad"} = {"ident" => "t"}
615         if $self->api_name =~ /\.ident/;
616
617     my $searched_for_something = 0;
618
619     if (ref $attr_value_pairs eq "ARRAY") {
620         $where->{"-or"} = [];
621         foreach (@$attr_value_pairs) {
622             next if @$_ != 2;
623             my ($def, $value) = @$_;
624             push @{$where->{"-or"}}, {
625                 "-and" => {
626                     "attr_value" => (ref $value) ?
627                         $value : {"ilike" => "%" . $value . "%"},
628                     "definition" => $def
629                 }
630             };
631         }
632         $searched_for_something = 1;
633     }
634
635     if ($li_states and @$li_states) {
636         $where->{"+jub"} = {"state" => $li_states};
637         $searched_for_something = 1;
638     }
639
640     if ($po_agencies and @$po_agencies) {
641         $where->{"+acqpo"} = {"ordering_agency" => $po_agencies};
642         $searched_for_something = 1;
643     }
644
645     if (not $searched_for_something) {
646         $e->rollback;
647         return new OpenILS::Event(
648             "BAD_PARAMS", note => "You have provided no search terms."
649         );
650     }
651
652     $query->{"where"} = $where;
653     my $lis = $e->json_query($query);
654
655     for my $li_id_obj (@$lis) {
656         my $li_id = $li_id_obj->{"lineitem"};
657         if($options->{"idlist"}) {
658             $conn->respond($li_id);
659         } else {
660             $conn->respond(
661                 retrieve_lineitem($self, $conn, $auth, $li_id, $options)
662             );
663         }
664     }
665     undef;
666 }
667
668
669 __PACKAGE__->register_method(
670     method    => 'lineitem_search_ident',
671     api_name  => 'open-ils.acq.lineitem.search.ident',
672     stream    => 1,
673     signature => {
674         desc => 'Performs a search against lineitem_attrs where ident is true',
675         params => [
676             {desc => 'Authentication token', type => 'string'},
677             {   desc => q/Search definition. Options are:
678                    attr_values : list of attribute values (required)
679                    li_states : list of lineitem states
680                    po_agencies : list of purchase order ordering agencies (org) ids
681                 /,
682                 type => 'object',
683             },
684             {   desc => q/
685                     Options hash.  Options are:
686                         idlist : if set, only return lineitem IDs
687                         clear_marc : if set, strip the MARC xml from the lineitem before delivery
688                         flesh_attrs : flesh lineitem attributes; 
689                 /,
690                 type => 'object',
691             }
692         ]
693     }
694 );
695
696 my $LI_ATTR_SEARCH = {
697     select => { acqlia => ['lineitem'] },
698     from   => {
699         acqlia => {
700             acqliad => {
701                 field => 'id',
702                 fkey  => 'definition'
703             },
704             jub => {
705                 field => 'id',
706                 fkey  => 'lineitem',
707                 join  => {
708                     acqpo => {
709                         field => 'id',
710                         fkey  => 'purchase_order'
711                     }
712                 }
713             }
714         }
715     }
716 };
717
718 sub lineitem_search_ident {
719     my($self, $conn, $auth, $search, $options) = @_;
720     my $e = new_editor(authtoken=>$auth, xact=>1);
721     return $e->event unless $e->checkauth;
722     # XXX needs permissions consideration
723
724     return [] unless $search;
725     my $attr_values = $search->{attr_values};
726     my $li_states = $search->{li_states};
727     my $po_agencies = $search->{po_agencies}; # XXX if none, base it on perms
728
729     my $where_clause = {
730         '-or' => [],
731         '+acqlia' => {
732             '+acqliad' => {ident => 't'},
733         }
734     };
735
736     push(@{$where_clause->{'-or'}}, {attr_value => {ilike => "%$_%"}}) for @$attr_values;
737
738     $where_clause->{'+jub'} = {state => {in => $li_states}}
739         if $li_states and @$li_states;
740
741     $where_clause->{'+acqpo'} = {ordering_agency => $po_agencies} 
742         if $po_agencies and @$po_agencies;
743
744     $LI_ATTR_SEARCH->{where} = $where_clause;
745
746     my $lis = $e->json_query($LI_ATTR_SEARCH);
747
748     for my $li_id_obj (@$lis) {
749         my $li_id = $li_id_obj->{lineitem};
750         if($$options{idlist}) {
751             $conn->respond($li_id);
752         } else {
753             my $li;
754             if($$options{flesh_attrs}) {
755                 $li = $e->retrieve_acq_lineitem([
756                     $li_id, {flesh => 1, flesh_fields => {jub => ['attributes']}}])
757             } else {
758                 $li = $e->retrieve_acq_lineitem($li_id);
759             }
760             $li->clear_marc if $$options{clear_marc};
761             $conn->respond($li);
762         }
763     }
764     return undef;
765 }
766
767
768 __PACKAGE__->register_method(
769     method    => 'retrieve_lineitem_detail',
770     api_name  => 'open-ils.acq.lineitem_detail.retrieve',
771     authoritative => 1,
772     signature => {
773         desc   => q/Updates a lineitem detail/,
774         params => [
775             { desc => 'Authentication token',              type => 'string' },
776             { desc => 'id of lineitem_detail to retrieve', type => 'number' },
777         ],
778         return => { desc => 'object on success, Event on failure' }
779     }
780 );
781 sub retrieve_lineitem_detail {
782     my($self, $conn, $auth, $li_detail_id) = @_;
783     my $e = new_editor(authtoken=>$auth);
784     return $e->event unless $e->checkauth;
785
786     my $li_detail = $e->retrieve_acq_lineitem_detail($li_detail_id)
787         or return $e->event;
788
789     if($li_detail->fund) {
790         my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->event;
791         return $e->event unless 
792             $e->allowed('MANAGE_FUND', $fund->org, $fund);
793     }
794
795     # XXX check lineitem perms
796     return $li_detail;
797 }
798
799
800 __PACKAGE__->register_method(
801     method    => 'approve_lineitem',
802     api_name  => 'open-ils.acq.lineitem.approve',
803     signature => {
804         desc   => 'Mark a lineitem as approved',
805         params => [
806             { desc => 'Authentication token', type => 'string' },
807             { desc => 'lineitem ID',          type => 'number' }
808         ],
809         return => { desc => '1 on success, Event on error' }
810     }
811 );
812 sub approve_lineitem {
813     my($self, $conn, $auth, $li_id) = @_;
814     my $e = new_editor(xact=>1, authtoken=>$auth);
815     return $e->die_event unless $e->checkauth;
816
817     # XXX perm checks for each lineitem detail
818
819     my $li = $e->retrieve_acq_lineitem($li_id)
820         or return $e->die_event;
821
822     return OpenILS::Event->new('ACQ_LINEITEM_APPROVED', payload => $li_id)
823         if $li->state eq 'approved';
824
825     my $details = $e->search_acq_lineitem_detail({lineitem => $li_id});
826     return OpenILS::Event->new('ACQ_LINEITEM_NO_COPIES', payload => $li_id)
827         unless scalar(@$details) > 0;
828
829     for my $detail (@$details) {
830         return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_FUND', payload => $detail->id)
831             unless $detail->fund;
832
833         return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_ORG', payload => $detail->id)
834             unless $detail->owning_lib;
835     }
836     
837     $li->state('approved');
838     $li->edit_time('now');
839     $e->update_acq_lineitem($li) or return $e->die_event;
840
841     $e->commit;
842     return 1;
843 }
844
845
846
847 __PACKAGE__->register_method(
848     method    => 'set_lineitem_attr',
849     api_name  => 'open-ils.acq.lineitem_usr_attr.set',
850     signature => {
851         desc   => 'Sets a lineitem_usr_attr value',
852         params => [
853             { desc => 'Authentication token', type => 'string' },
854             { desc => 'Lineitem ID',          type => 'number' },
855             { desc => 'Attr name',            type => 'string' },
856             { desc => 'Attr value',           type => 'string' }
857         ],
858         return => { desc => '1 on success, Event on error' }
859     }
860 );
861
862 __PACKAGE__->register_method(
863     method    => 'set_lineitem_attr',
864     api_name  => 'open-ils.acq.lineitem_local_attr.set',
865     signature => {
866         desc   => 'Sets a lineitem_local_attr value',
867         params => [
868             { desc => 'Authentication token', type => 'string' },
869             { desc => 'Lineitem ID',          type => 'number' },
870             { desc => 'Attr name',            type => 'string' },
871             { desc => 'Attr value',           type => 'string' }
872         ],
873         return => { desc => 'ID of the attr object on success, Event on error' }
874     }
875 );
876
877
878 sub set_lineitem_attr {
879     my($self, $conn, $auth, $li_id, $attr_name, $attr_value) = @_;
880     my $e = new_editor(xact=>1, authtoken=>$auth);
881     return $e->die_event unless $e->checkauth;
882
883     # XXX perm
884
885     my $attr_type = $self->api_name =~ /local_attr/ ?
886         'lineitem_local_attr_definition' : 'lineitem_usr_attr_definition';
887
888     my $attr = $e->search_acq_lineitem_attr({
889         lineitem => $li_id, 
890         attr_type => $attr_type,
891         attr_name => $attr_name})->[0];
892
893     my $find = "search_acq_$attr_type";
894
895     if($attr) {
896         $attr->attr_value($attr_value);
897         $e->update_acq_lineitem_attr($attr) or return $e->die_event;
898     } else {
899         $attr = Fieldmapper::acq::lineitem_attr->new;
900         $attr->lineitem($li_id);
901         $attr->attr_type($attr_type);
902         $attr->attr_name($attr_name);
903         $attr->attr_value($attr_value);
904
905         my $attr_def_id = $e->$find({code => $attr_name}, {idlist=>1})->[0] 
906             or return $e->die_event;
907         $attr->definition($attr_def_id);
908         $e->create_acq_lineitem_attr($attr) or return $e->die_event;
909     }
910
911     $e->commit;
912     return $attr->id;
913 }
914
915 __PACKAGE__->register_method(
916     method    => 'get_lineitem_attr_defs',
917     api_name  => 'open-ils.acq.lineitem_attr_definition.retrieve.all',
918     authoritative => 1,
919     signature => {
920         desc   => 'Retrieve lineitem attr definitions',
921         params => [ { desc => 'Authentication token', type => 'string' }, ],
922         return => { desc => 'List of attr definitions' }
923     }
924 );
925
926 sub get_lineitem_attr_defs {
927     my($self, $conn, $auth) = @_;
928     my $e = new_editor(authtoken=>$auth);
929     return $e->event unless $e->checkauth;
930     my %results;
931     for my $type (qw/generated marc local usr provider/) {
932         my $call = "retrieve_all_acq_lineitem_${type}_attr_definition";
933         $results{$type} = $e->$call;
934     }
935     return \%results;
936 }
937
938
939 __PACKAGE__->register_method(
940     method    => 'lineitem_note_CUD_batch',
941     api_name  => 'open-ils.acq.lineitem_note.cud.batch',
942     stream    => 1,
943     signature => {
944         desc   => q/Manage lineitem notes/,
945         params => [
946             { desc => 'Authentication token',             type => 'string' },
947             { desc => 'List of lineitem_notes to manage', type => 'array'  },
948         ],
949         return =>
950           { desc => 'Streaming response of current position in the array' }
951     }
952 );
953
954 sub lineitem_note_CUD_batch {
955     my($self, $conn, $auth, $li_notes) = @_;
956
957     my $e = new_editor(xact=>1, authtoken=>$auth);
958     return $e->die_event unless $e->checkauth;
959     # XXX perms
960
961     my $total = @$li_notes;
962     my $count = 0;
963
964     for my $note (@$li_notes) {
965
966         $note->editor($e->requestor->id);
967         $note->edit_time('now');
968
969         if($note->isnew) {
970             $note->creator($e->requestor->id);
971             $note = $e->create_acq_lineitem_note($note) or return $e->die_event;
972
973         } elsif($note->isdeleted) {
974             $e->delete_acq_lineitem_note($note) or return $e->die_event;
975
976         } elsif($note->ischanged) {
977             $e->update_acq_lineitem_note($note) or return $e->die_event;
978         }
979
980         if(!$note->isdeleted) {
981             $note = $e->retrieve_acq_lineitem_note([
982                 $note->id, {
983                     "flesh" => 1, "flesh_fields" => {"acqlin" => ["alert_text"]}
984                 }
985             ]);
986         }
987
988         $conn->respond({maximum => $total, progress => ++$count, note => $note});
989     }
990
991     $e->commit;
992     return {complete => 1};
993 }
994
995 __PACKAGE__->register_method(
996     method => 'ranged_line_item_alert_text',
997     api_name => 'open-ils.acq.line_item_alert_text.ranged.retrieve.all');   # TODO: signature
998
999 sub ranged_line_item_alert_text {
1000     my($self, $conn, $auth, $org_id, $depth) = @_;
1001     my $e = new_editor(authtoken => $auth);
1002     return $e->event unless $e->checkauth;
1003     return $e->event unless $e->allowed('ADMIN_ACQ_LINEITEM_ALERT_TEXT', $org_id);
1004     return $e->search_acq_lineitem_alert_text(
1005         {owning_lib => $U->get_org_full_path($org_id, $depth)});
1006 }
1007
1008
1009 __PACKAGE__->register_method(
1010     method => "retrieve_lineitem_by_copy_id",
1011     api_name => "open-ils.acq.lineitem.retrieve.by_copy_id",
1012     authoritative => 1,
1013     signature => {
1014         desc => q/Manage lineitem notes/,
1015         params => [
1016             {desc => "Authentication token", type => "string"},
1017             {desc => "Evergreen internal copy ID", type => "number"},
1018             {desc => "Hash of options (see open-ils.acq.lineitem.retrieve",
1019                 type => "object"}
1020         ],
1021         return => {
1022             desc => "Lineitem associated with given copy",
1023             type => "object", class => "jub"
1024         }
1025     }
1026 );
1027
1028 sub retrieve_lineitem_by_copy_id {
1029     my ($self, $conn, $auth, $object_id, $options) = @_;
1030
1031     my $e = new_editor("authtoken" => $auth);
1032     return $e->die_event unless $e->checkauth;
1033
1034     my $result = $e->json_query({
1035         "select" => {"acqlid" => ["lineitem"]},
1036         "from" => "acqlid",
1037         "where" => {"eg_copy_id" => $object_id}
1038     })->[0] or do {
1039         $e->disconnect;
1040         return new OpenILS::Event("ACQ_LINEITEM_NOT_FOUND");
1041     };
1042
1043     my $li = retrieve_lineitem_impl($e, $result->{"lineitem"}, $options) or
1044         return $e->die_event;
1045
1046     $e->disconnect;
1047     return $li;
1048 }
1049
1050 1;