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